← All articles

WordPress Development

How to Review AI-Generated WordPress Code Before It Ships

AI tools write plausible WordPress PHP that fails in predictable ways: invented hooks, missing escaping, wrong load order, and queries on every request. A review checklist with before-and-after examples.

6 min read Mark Ashton

  • ai
  • security
  • wordpress
  • snippet engineer
Abstract cover artwork for How to Review AI-Generated WordPress Code Before It Ships

AI assistants are now the fastest way to get a first draft of a WordPress snippet. They are also very good at producing code that looks right, passes a glance, and fails on a real site.

The failures are predictable. After reviewing a lot of AI-written WordPress PHP, the same eight problems account for almost every bug. This is the checklist, with a before-and-after for each, plus the tooling that catches them automatically.

Why AI-written WordPress code needs its own review

General-purpose models learned WordPress from fifteen years of tutorials, forum answers, and plugin code — much of it outdated, insecure, or written for a different version. They also have no idea what is installed on *your* site. So they:

  • Mix current APIs with patterns from 2014
  • Invent hooks and functions that sound plausible
  • Assume plugins are active and loaded
  • Optimize for "works in the demo," not "works on every request of a busy store"

None of this means you should not use AI. It means the review has to look for these specific failure modes.

1. Does every hook and function actually exist?

This is the most common problem and the easiest to miss, because invented names look exactly like real ones. woocommerce_after_cart_totals_row sounds right. Check whether it is.

How to check:

  • Search the WordPress Developer Reference for core hooks and functions
  • For plugins, search the plugin's source: grep -rn "do_action( 'hook_name'" wp-content/plugins/woocommerce
  • In a running site, has_action( 'hook_name' ) tells you if anything is attached; adding a temporary error_log inside your callback tells you if it fires

A callback on a hook that never fires does not error. It just silently does nothing, which is often worse.

2. Is output escaped?

AI drafts routinely echo variables directly:

// Before
echo '<div class="notice">' . $message . '</div>';
echo '<a href="' . $url . '">' . $label . '</a>';
// After
echo '<div class="notice">' . esc_html( $message ) . '</div>';
echo '<a href="' . esc_url( $url ) . '">' . esc_html( $label ) . '</a>';

Escape at the point of output, with the function that matches the context: esc_html for text, esc_attr for attributes, esc_url for URLs, wp_kses_post for trusted HTML. "It is only an admin notice" is not a reason to skip it.

3. Are input, permissions, and intent checked?

Anything that reads a request and changes data needs three checks: sanitize the input, confirm the user is allowed, and confirm they meant to do it.

// Before
add_action( 'admin_post_acme_save', function () {
	update_option( 'acme_banner', $_POST['banner'] );
	wp_redirect( admin_url() );
} );
// After
add_action( 'admin_post_acme_save', function () {
	if ( ! current_user_can( 'manage_options' ) ) {
		wp_die( esc_html__( 'Not allowed.', 'acme' ), 403 );
	}

	check_admin_referer( 'acme_save' );

	$banner = isset( $_POST['banner'] ) ? sanitize_text_field( wp_unslash( $_POST['banner'] ) ) : '';
	update_option( 'acme_banner', $banner );

	wp_safe_redirect( admin_url( 'options-general.php?page=acme' ) );
	exit;
} );

Watch for the small ones too: wp_unslash before sanitizing, wp_safe_redirect instead of wp_redirect, and exit after redirecting.

4. Is SQL prepared?

Models still concatenate variables into queries:

// Before
$rows = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}orders WHERE email = '$email'" );
// After
$rows = $wpdb->get_results(
	$wpdb->prepare( "SELECT * FROM {$wpdb->prefix}orders WHERE email = %s", $email )
);

Better still, ask whether raw SQL is needed at all. WP_Query, get_posts, wc_get_orders, and get_users handle caching and escaping for you.

5. Does it run at the right time?

A snippet's top-level code runs when the snippet loads, which may be before other plugins, the current user, or the query are ready. AI drafts often call things too early:

// Before — WooCommerce may not be loaded, and there is no current user yet
if ( WC()->cart->get_cart_contents_count() > 5 ) { /* ... */ }
// After
add_action( 'wp', function () {
	if ( ! function_exists( 'WC' ) || ! WC()->cart ) {
		return;
	}

	if ( WC()->cart->get_cart_contents_count() > 5 ) { /* ... */ }
} );

Rules of thumb: plugin APIs after plugins_loaded, user checks after init, conditional tags such as is_checkout() after wp.

6. Will it survive a missing dependency?

If WooCommerce, ACF, or a form plugin is deactivated for an update, a snippet that assumes it exists becomes a fatal. Every dependency needs a guard: class_exists, function_exists, or defined. Declared functions need function_exists guards too, so a duplicate import does not trigger "Cannot redeclare."

7. What does it cost on every request?

AI optimizes for correctness on one page view, not for performance across a million. Look for:

  • get_posts( array( 'posts_per_page' => -1 ) ) in a global hook
  • wp_remote_get on page load without caching
  • Queries inside loops
  • Work in init that only matters on one page
// After — cache the remote call for an hour
$rates = get_transient( 'acme_fx_rates' );

if ( false === $rates ) {
	$response = wp_remote_get( 'https://api.example.com/rates', array( 'timeout' => 5 ) );
	$rates    = is_wp_error( $response ) ? array() : json_decode( wp_remote_retrieve_body( $response ), true );
	set_transient( 'acme_fx_rates', $rates, HOUR_IN_SECONDS );
}

Then scope the snippet with conditional loading so it only runs where it is needed.

8. Is anything outdated or dangerous?

Red flags that should always stop a merge:

  • eval, create_function, assert with strings, preg_replace with the /e modifier
  • unserialize on anything a user or remote server can influence
  • extract( $_POST ) or $variable names built from input
  • file_get_contents on URLs instead of the HTTP API
  • Writing files to the web root
  • jQuery patterns such as .live() or a bundled jQuery copy

Also check PHP compatibility with your oldest production host. The WordPress 7.0 and PHP 8.4 guide lists what commonly breaks.

Automate the boring half

A human review should focus on logic and intent. Let tools catch the mechanical issues:

  • php -l for syntax on every PHP version you run
  • PHPCS with WordPress Coding Standards — flags missing escaping, unsanitized input, and non-prepared SQL
  • PHPStan with WordPress stubs — flags calls to functions and methods that do not exist, which catches many invented APIs
composer require --dev wp-coding-standards/wpcs phpstan/phpstan szepeviktor/phpstan-wordpress
vendor/bin/phpcs --standard=WordPress-Extra snippets/
vendor/bin/phpstan analyse snippets/ --level=5

Run them in CI on every pull request. The GitHub workflow guide shows how to wire up a lint job for a snippet repository.

Make the tool run the checks before you see the code

The best time to catch these problems is before a human looks at all. This is the case for agents that execute and check their own code instead of returning a text block.

SnipVault's Snippet Engineer runs every PHP snippet in an isolated sandbox request to catch fatals, scores it with the security audit — which flags patterns such as unescaped output and unprepared queries — and cannot deploy code that exceeds your configured risk ceiling. You still review the result, but you review code that has already run once and passed a scan. More on how that loop works in Cursor for WordPress.

The checklist

Before approving any AI-written WordPress code:

  1. Every hook and function exists in the versions you run
  2. All output is escaped for its context
  3. Input is sanitized, permissions checked, nonces verified
  4. SQL is prepared, or replaced with a core API
  5. Code runs on the right hook, not at load time
  6. Every dependency is guarded
  7. Nothing expensive runs on every request
  8. No dangerous or deprecated functions
  9. Lint, PHPCS, and PHPStan pass
  10. You can explain what it does in one sentence

If you cannot do number 10, ask the AI to simplify it. Code you do not understand is code you cannot safely maintain — regardless of who wrote it.

Next

Ready to upgrade your snippet workflow?

GitHub sync, the Snippet Engineer, and security auditing — in one WordPress plugin.