Independent Editorial DeskWordPress Releases, Builds, and Operations
Back to Archive
Hardening NotesImplementation NotesThreat Research

WordPress wpdb::prepare Example: Prevent SQL Injection in Custom Queries

Learn how to use wpdb::prepare in WordPress custom queries with safe placeholders, LIKE searches, IN clauses, validation, and SQL injection prevention checks.

Published

April 16, 2026

Reading Time

5 min read

Updated

May 4, 2026

Abstract database cylinders and query panels representing prepared SQL in WordPress.
Control LedgerHardening Notes

Hardening Notes

Baselines, access reduction, and default settings that stand up in production.

Best For

Teams preparing, launching, or maintaining WordPress as a backend service in a production stack.

Primary Topics

Hardening NotesImplementation NotesThreat Research

Editorial Focus

Control Ledger: Baselines, access reduction, and default settings that stand up in production. Updated on May 4, 2026.

Full Report

Last reviewed: April 16, 2026

WordPress SQL injection prevention is a high-intent search because developers eventually reach for $wpdb when built-in APIs do not fit. The risky moment is not using $wpdb itself. The risk is building SQL strings with request data, shortcode attributes, AJAX parameters, or admin form values without prepared placeholders.

This guide shows how to use $wpdb->prepare() for safe custom queries, when to avoid custom SQL entirely, and which validation steps matter before code reaches production. It is written for practical plugin work, not abstract database theory.

wpdb prepare example: what must be protected

A useful wpdb prepare example should protect every value that enters custom SQL, not just obvious search fields. Email addresses, IDs, shortcode attributes, AJAX parameters, report filters, dates, and status values all need a placeholder or a stricter WordPress API. Prepared SQL also does not decide whether the current user should run the query, so pair it with capability checks and nonce checks on privileged admin or AJAX paths.

Who this is for

This article is for WordPress plugin developers, agencies maintaining custom code, and operators auditing plugins that store or query custom tables. If a feature receives input and then builds SQL, it needs a careful review.

Use WordPress APIs before custom SQL

Before writing SQL, check whether a higher-level WordPress API already solves the problem. WP_Query, metadata APIs, taxonomy APIs, user APIs, and options APIs often provide enough functionality with less risk. Custom SQL is useful when you truly need custom table access, aggregate reporting, or a query shape WordPress APIs do not support cleanly.

If custom SQL is necessary, use prepared statements, validate input, keep result sets bounded, and avoid exposing raw database structure to frontend code.

Unsafe example: string interpolation

The pattern below is dangerous because user-controlled input is inserted directly into a SQL string.

$email = $_GET['email'] ?? '';

$row = $wpdb->get_row(
	"SELECT * FROM {$wpdb->prefix}vulnwp_leads WHERE email = '{$email}'"
);

Even if the field usually contains a normal email address, the database query now depends on untrusted input. Escaping manually is also easy to get wrong. Use placeholders instead.

Safe example with $wpdb->prepare()

global $wpdb;

$email = isset( $_GET['email'] )
	? sanitize_email( wp_unslash( $_GET['email'] ) )
	: '';

if ( ! is_email( $email ) ) {
	return null;
}

$table = $wpdb->prefix . 'vulnwp_leads';

$row = $wpdb->get_row(
	$wpdb->prepare(
		"SELECT id, email, created_at
		FROM {$table}
		WHERE email = %s
		LIMIT 1",
		$email
	)
);

The placeholder %s tells WordPress to prepare the email as a string value. The input is also sanitized and validated before the query because prepared statements do not replace business validation.

Common placeholders

  • %s for strings.
  • %d for integers.
  • %f for floats.
  • %i for identifiers where supported by the running WordPress version.

Use the placeholder that matches the value. Do not quote placeholders manually inside the SQL string. WordPress handles that as part of preparation.

Safe search query example

Search queries need special care because wildcards are part of the intended behavior. Escape the LIKE value, then prepare the final SQL.

global $wpdb;

$search = isset( $_GET['q'] )
	? sanitize_text_field( wp_unslash( $_GET['q'] ) )
	: '';

if ( strlen( $search ) < 3 ) {
	return array();
}

$like  = '%' . $wpdb->esc_like( $search ) . '%';
$table = $wpdb->prefix . 'vulnwp_leads';

$results = $wpdb->get_results(
	$wpdb->prepare(
		"SELECT id, email, created_at
		FROM {$table}
		WHERE email LIKE %s
		ORDER BY created_at DESC
		LIMIT 20",
		$like
	)
);

This example also limits results. Security and performance are connected: unbounded search endpoints can become a production load problem even when they are not directly injectable.

Safe IN clause example

For a variable list of IDs, sanitize the IDs first, build the correct number of placeholders, then pass the values into prepare().

global $wpdb;

$ids = isset( $_GET['ids'] ) ? (array) $_GET['ids'] : array();
$ids = array_values( array_filter( array_map( 'absint', $ids ) ) );

if ( empty( $ids ) ) {
	return array();
}

$placeholders = implode( ', ', array_fill( 0, count( $ids ), '%d' ) );
$table        = $wpdb->prefix . 'vulnwp_leads';

$sql = $wpdb->prepare(
	"SELECT id, email, created_at
	FROM {$table}
	WHERE id IN ({$placeholders})
	LIMIT 50",
	$ids
);

$rows = $wpdb->get_results( $sql );

Do not pass raw comma-separated strings into SQL. Convert them into typed values first.

Production checklist

  • Use WordPress APIs instead of custom SQL when they fit the use case.
  • Use $wpdb->prepare() for values that enter custom SQL.
  • Sanitize and validate input before building the query.
  • Use $wpdb->esc_like() for LIKE patterns.
  • Bound result sets with explicit LIMIT values where appropriate.
  • Use capability checks and nonce checks before privileged queries.
  • Log or handle database errors without exposing raw SQL to visitors.
  • Test invalid input, empty input, long input, and low-privilege requests.

Common mistakes

  • Concatenating request data into SQL. This is the classic injection path.
  • Sanitizing but not preparing. Sanitization helps, but prepared placeholders are still the correct SQL boundary.
  • Preparing the wrong part of the query. Placeholders protect values, not arbitrary SQL fragments.
  • Trusting admin-only screens too much. Admin tools can still be abused if CSRF or capability checks are missing.
  • Returning raw database errors publicly. Error handling should not leak implementation details.

Where this fits in a plugin review

SQL review should happen whenever a plugin adds custom tables, reporting screens, import tools, or AJAX search endpoints. Pair it with our WordPress dbDelta custom table example, WordPress nonce example, and plugin vulnerability response checklist. Prepared SQL is only one part of a safe request path.

How to audit an existing plugin quickly

Start by searching for direct database access and superglobals in the same files. The goal is not to prove a vulnerability from grep alone. The goal is to find code paths where request data may enter SQL.

# Search custom code for direct database usage.
grep -R "$wpdb->" wp-content/plugins/your-plugin

# Look for prepared statements.
grep -R "$wpdb->prepare" wp-content/plugins/your-plugin

# Find request input near database code.
grep -R "$_GET\|$_POST\|$_REQUEST" wp-content/plugins/your-plugin

For every match, trace the input path. Identify where the value comes from, how it is sanitized, whether it is validated against a business rule, whether the SQL uses placeholders, and whether the action has a capability or nonce boundary. A query can be prepared correctly and still be dangerous if any logged-in user can trigger an expensive report or retrieve data they should not see.

Operational signals after deployment

After shipping custom SQL, monitor slow queries, PHP errors, unexpected empty result sets, and spikes in admin-ajax or REST traffic. SQL safety is not only about injection. It is also about predictable behavior under production data volume. A query that works on 50 rows in staging may hurt a real site with years of content and metadata.

References and further reading

Popular Guides

Popular WordPress guides to read next.

These articles connect recurring production concerns: implementation details, updates, troubleshooting, recovery paths, and operational cleanup.

Continue Reading

More from the archive.

Diagnostic dashboard scene representing a WordPress Site Health review before major updates.
01Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Site Health Check Before Major Updates: What to Review First

A pre-update WordPress Site Health checklist covering loopbacks, connectivity, debug settings, and environment readiness.

Structured data and route review scene representing permalink validation after a WordPress migration.
02Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Permalink Checklist After Migration: Catch URL Problems Early

A post-migration WordPress permalink checklist for checking rewrite rules, post URLs, archives, and redirect noise.

Technical media workspace representing image preparation and optimization before upload to WordPress.
03Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Image Optimization Checklist: What to Fix Before Upload

A practical WordPress image optimization checklist covering dimensions, compression, formats, and Media settings before upload.