SQL Injection in WordPress Plugins: How to Avoid It
SQL injection in WordPress plugins almost never happens because a developer typed raw SQL on purpose. It happens because a variable from $_GET, $_POST, or a REST API request got concatenated into a query string without going through $wpdb->prepare() first. That one missed step is responsible for a huge share of the critical vulnerabilities logged against WordPress.org plugins every year.
This is a practical breakdown of where it happens, what the vulnerable code actually looks like next to the fixed version, and the checklist to run before you submit anything to the plugin directory.
Why SQL Injection in WordPress Plugins Keeps Happening in 2026
WordPress core's database layer, $wpdb, is safe when used correctly. The vulnerability almost always lives in plugin or theme code that bypasses the safe methods — usually in a custom admin report, a search filter, an AJAX handler, or a shortcode that queries a custom table.
- WPScan's vulnerability database consistently lists SQL injection among the top three vulnerability classes in WordPress plugins, year after year.
- Most cases involve plugins with their own custom database tables — think form plugins storing submissions, membership plugins storing access logs, or e-commerce plugins storing order metadata.
- Popular plugins aren't immune. High-install-count plugins are bigger targets precisely because a single flaw can expose hundreds of thousands of sites at once. Check current install counts and update cadence on WordPress plugin rankings before trusting a dependency.
What the Vulnerable Code Actually Looks Like
Here's the pattern that shows up over and over in disclosed vulnerabilities: a value from the request gets dropped straight into a query string using string concatenation or interpolation.
The vulnerable version
$search = $_GET['s'];
$results = $wpdb->get_results(
"SELECT * FROM {$wpdb->prefix}my_plugin_entries WHERE title LIKE '%$search%'"
);
An attacker can pass s=%' OR '1'='1 (or worse, a stacked query) and the database executes whatever they wrote, not what the developer intended.
The safe version
$search = sanitize_text_field( $_GET['s'] );
$like = '%' . $wpdb->esc_like( $search ) . '%';
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}my_plugin_entries WHERE title LIKE %s",
$like
)
);
$wpdb->prepare() uses placeholders — %s for strings, %d for integers, %f for floats — and escapes the value before it ever touches the query. esc_like() additionally escapes the wildcard characters % and _ so a search term can't manipulate the LIKE pattern itself.
| Pattern | Risk | Fix |
|---|---|---|
| String concatenation into SQL | Critical — direct injection | $wpdb->prepare() with placeholders |
| Table/column names from user input | Critical — placeholders can't quote identifiers | Whitelist against a fixed array of allowed names |
| LIKE queries with raw search terms | High — wildcard injection, data leakage | $wpdb->esc_like() plus %s placeholder |
| ORDER BY from a query string param | High — often forgotten since it's not a "value" | Whitelist allowed column names, never pass raw input |
$wpdb->insert() / update() without format arrays | Medium — type coercion can be exploited | Always pass the format array, e.g. array('%s','%d') |
Where SQL Injection Hides in Real Plugin Features
Some plugin categories carry more risk than others simply because of what they store and query:
- Form plugins — submissions get written to custom tables, then filtered and exported through admin screens. Contact Form 7 and other entries on our best WordPress form plugins comparison are worth studying for how they sanitize before storage.
- E-commerce plugins — order search, coupon lookups, and reporting dashboards query by SKU, email, or order ID straight from admin-ajax requests. WooCommerce locks this down with prepared statements throughout its order repository layer.
- Page builders and widget plugins — dynamic widgets that query posts or custom fields by an ID passed through a shortcode attribute or REST parameter. Elementor is a good reference for how a large, widget-heavy codebase sanitizes dynamic data sources.
- Caching and performance plugins — anything that purges or queries cache tables by URL or post ID, like LiteSpeed Cache, needs the same discipline even though it feels like "internal" data.
A Practical Checklist Before You Ship
- Grep your codebase for
$wpdb->query,get_results,get_row,get_var, andget_col— every single one should either contain no user input or run throughprepare(). - Never build table or column names from user input, even with
prepare()— placeholders quote values, not identifiers. Use a hardcoded whitelist array and match against it. - Use
$wpdb->insert(),update(),delete(), andreplace()instead of hand-written statements wherever possible, and always supply the format array. - Sanitize on input (
sanitize_text_field,absint,sanitize_key) and escape again on output — sanitization and SQL escaping solve different problems and neither replaces the other. - Pair every query-handling endpoint with proper nonce verification and capability checks — a SQL-safe query that any logged-out user can trigger is still a serious bug. Our guide to nonces and capability checks covers this in detail.
- Run the official Plugin Check plugin (PCP) from WordPress.org before submission — it flags unescaped
$wpdbcalls automatically, along with several other review-blocking issues. - Add PHPCS with the WordPress security ruleset (
WordPress.DB.PreparedSQL,WordPress.Security.EscapeOutput) to CI so the check runs on every commit, not just before release.
What It Costs You If You Get It Wrong
WordPress.org's security team pulls plugins from the directory the moment a confirmed SQL injection report comes in, often with no advance notice to the author. That means:
- Your plugin drops out of search and the rankings instantly — zero new installs until a patched version is reviewed and reinstated.
- Existing users get a dashboard warning recommending they deactivate the plugin, which tanks active-install retention even after you fix it.
- Security-focused aggregators (WPScan, Patchstack) publish the CVE permanently, and it shows up in due-diligence searches for years — including when a buyer is evaluating your plugin via tools like our competitor analysis tool.
If you're choosing between plugins to build on top of, or deciding whether a dependency is safe to recommend to clients, run it through our plugin & theme detector first to see what version a live site is running and cross-check it against known disclosures.
FAQ
What is SQL injection in a WordPress plugin, specifically?
It's when a plugin takes untrusted input — from a URL parameter, form field, or REST request — and inserts it directly into a SQL query string instead of passing it through $wpdb->prepare(). The attacker crafts input that changes the query's logic, letting them read, modify, or delete data they shouldn't have access to.
Does using $wpdb automatically make my plugin safe from SQL injection?
No. $wpdb is just WordPress's database access class — it doesn't escape anything unless you explicitly use prepare(), placeholders, or the built-in insert()/update()/delete() helpers with format arrays. Calling $wpdb->query() with a concatenated string is exactly as vulnerable as raw PHP mysqli code.
Is esc_sql() enough to prevent SQL injection?
esc_sql() escapes special characters but doesn't add quotes around the value or validate its type, so it's easy to misuse. WordPress's own documentation recommends $wpdb->prepare() as the primary method and treats esc_sql() as a fallback for edge cases where placeholders genuinely can't be used.
Can SQL injection happen in the WordPress REST API or AJAX handlers?
Yes, and it's one of the most common places it's found today. Custom REST routes and admin-ajax.php handlers that build queries from $request->get_param() or $_POST without sanitization and preparation are injectable exactly like any other endpoint — REST doesn't add any automatic protection.
How do I check if a plugin I'm using has had a SQL injection vulnerability?
Check the plugin's changelog on WordPress.org for security-related wording, search its slug on WPScan's vulnerability database, and verify the installed version against the patched version. Our plugin & theme detector can confirm exactly which version a live site is running.
Will a SQL injection report get my plugin removed from WordPress.org?
Yes, typically immediately and without warning once the security team verifies the report. The plugin stays closed until you submit a patched version and it passes review, during which time you get zero visibility in search or the directory listings.
Does fixing security issues actually affect plugin rankings and downloads?
Indirectly, yes — a directory removal halts installs completely, and the active-install count (a major ranking signal) stalls or drops while the plugin is closed. Beyond the outage itself, security incidents show up in due-diligence research that marketers and competitors run when comparing plugins, which can cost trust long after the patch ships.
Frequently Asked Questions
WP Stats Team
The WP Stats team analyzes data from 69,000+ WordPress plugins and 28,000+ themes on WordPress.org. We publish practical, data-backed guidance on plugin SEO, growth, and competitive analysis for developers and marketers.
View all articles by WP Stats Team