Every one of our 11 plugins has gone through WordPress.org's Plugin Check — the automated and human review that decides whether your plugin reaches the directory at all. The first couple of submissions taught us more than any documentation page did, mostly because the failures were never about big, obvious mistakes. They were small, repeatable things that are easy to miss under deadline pressure.
This isn't a reproduction of the official guidelines — WordPress.org already documents those well. It's the shortlist of things that actually fail review in practice, plus the fix that stuck once we started applying it by default.
The single most common rejection reason we've seen. Any raw SQL touching user input — even indirectly — gets flagged. The fix isn't just $wpdb->prepare(), it's using it correctly, including for table names.
// Wrong — table name interpolated directly
$wpdb->get_results( "SELECT * FROM {$wpdb->prefix}my_table WHERE id = $id" );
// Right — %i for identifiers (WP 6.2+), %d for the value
$wpdb->get_results(
$wpdb->prepare( "SELECT * FROM %i WHERE id = %d", $wpdb->prefix . 'my_table', $id )
);
If you need a dynamic WHERE clause built from several optional filters, Plugin Check's static analyzer sometimes can't follow the logic even when it's safe. In that specific case, a scoped phpcs:disable / phpcs:enable pair around just the query-building block is acceptable — but only after you've confirmed every value going in is actually escaped. Never use phpcs:ignore as a reflex to silence a warning you haven't actually investigated.
Every register_setting() call needs a real sanitize_callback, not a placeholder. Every $_POST / $_GET read needs sanitization appropriate to what it's used for — sanitize_text_field() for plain strings, absint() for IDs, sanitize_email() for emails. Plugin Check catches raw superglobal reads even when you "know" the value is safe downstream.
The pattern that saves the most review cycles: sanitize at the point of input, escape at the point of output. Don't do one and assume it covers the other.
If you register a REST route and skip permission_callback, Plugin Check flags it immediately — and rightly so, since it defaults to public access. Even for intentionally public endpoints, set permission_callback explicitly to '__return_true' so it's clear the openness was a decision, not an oversight.
Hardcoded <script> tags in template output, or assets enqueued without a version string, both get flagged. Every asset needs wp_enqueue_script() / wp_enqueue_style() with a real version number tied to your plugin version — not null, which defeats cache-busting on updates.
The text domain in every __(), _e() and similar call has to exactly match the plugin's slug, declared in the main plugin file header. This one is easy to get wrong when you copy boilerplate from an older plugin and forget to update every string's domain — Plugin Check checks every single translatable string, not just a sample.
What this looked like for us
On Login Guard's 2FA release, the review caught a double-URL-encoded issuer string in the TOTP otpauth:// URI — subtle enough that it worked in testing but would've broken some authenticator apps in production. That's the value of the review: it catches the class of bug that passes a casual test but fails for a subset of real users.
The Stable tag in readme.txt has to match the version in your main plugin file, exactly. Tested up to needs to reflect a WordPress version you've actually verified against, not just the latest number copy-pasted in. And the short description has a hard character limit that Plugin Check will enforce even if it renders fine in your editor.
Every one of these failures has the same root cause: something that works fine in a quick manual test but breaks an invariant Plugin Check is built to catch. None of them are hard to fix once flagged — the real cost is the review round-trip, which can add days to a release. Building these checks into your own pre-submission routine is strictly faster than discovering them from WordPress.org's review queue.
If you're maintaining more than one or two plugins, it's worth turning this list into an actual pre-commit checklist rather than relying on memory — that's effectively what we did after the second or third submission round.
We handle Plugin Check compliance as a standard part of every WordPress build — not an afterthought before submission.
Get a Free Quote →