New: “AI + WordPress” lessons just dropped — build smarter sites in half the time.

Vibe Coding a WordPress Plugin: From Prompt to Production

Build a real WordPress plugin with AI: write the prompt, review the code for security, test in WordPress Playground, and ship it with confidence.

Today’s lab session is a fun one. We are going to build a real WordPress plugin by describing it in plain English, letting an AI assistant write the code, and then doing the part that separates hobby experiments from production software: reviewing, testing, and shipping it responsibly. Lab coats on.

What Is Vibe Coding?

“Vibe coding” is a term popularized by AI researcher Andrej Karpathy in early 2025. It describes a style of programming where you describe what you want in natural language, let an AI model write the code, run it, and iterate by describing what to change, often without reading every line closely.

For quick prototypes and personal tools, that is genuinely empowering. For code running on a public website, Professor Press adds one amendment: vibe the first draft, review the final draft.

Heads up: AI-generated code can look polished and still contain security holes, outdated functions, or logic bugs. A plugin runs with full access to your site, so anything going live deserves the same review you would give code from a stranger.

Where vibe coding shines

  • Small, self-contained features (a shortcode, a settings page, a custom block).
  • Prototypes you want to test before hiring a developer.
  • Learning: asking the AI to explain each part teaches you WordPress APIs quickly.

Where it struggles

  • Large codebases where changes ripple across many files.
  • Security-critical features like payments, authentication, or file uploads.
  • Anything you cannot test or would not be able to debug at 2 a.m.

Step 1: Write a Precise Prompt

Vague prompts produce vague code. Tell the AI exactly what the feature does, where it appears, and which standards to follow. Here is the prompt we used:

Write a small WordPress plugin called "WPP Reading Time Badge".
- Show "X min read" above the content of single blog posts only.
- Add a Settings > Reading Time page using the Settings API with one
  number field: words per minute (default 200, allowed 50-1000).
- Follow WordPress coding standards: ABSPATH guard, prefix everything
  with wpp_rt_, sanitize input, escape output, capability checks,
  translatable strings with text domain "wpp-reading-time".
- Include an uninstall.php that deletes the option.
- No external libraries, no tracking, no admin notices beyond errors.

Tip: Naming the standards in your prompt (sanitize, escape, prefix, capability checks) noticeably improves the output. The AI cannot follow rules you never mention.

Step 2: The Generated Plugin

After a couple of rounds of iteration, here is the main plugin file, wpp-reading-time/wpp-reading-time.php:

<?php
/**
 * Plugin Name:       WPP Reading Time Badge
 * Description:       Shows an estimated reading time badge above single posts.
 * Version:           1.0.0
 * Requires at least: 6.0
 * Requires PHP:      7.4
 * Author:            WordPress Professor
 * License:           GPL-2.0-or-later
 * Text Domain:       wpp-reading-time
 */

// Stop direct access to this file.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Register the setting, section and field with the Settings API.
 */
function wpp_rt_register_settings() {
	register_setting(
		'wpp_rt_settings',
		'wpp_rt_words_per_minute',
		array(
			'type'              => 'integer',
			'sanitize_callback' => 'wpp_rt_sanitize_wpm',
			'default'           => 200,
		)
	);

	add_settings_section(
		'wpp_rt_main',
		__( 'Reading speed', 'wpp-reading-time' ),
		'__return_false',
		'wpp-reading-time'
	);

	add_settings_field(
		'wpp_rt_words_per_minute',
		__( 'Words per minute', 'wpp-reading-time' ),
		'wpp_rt_render_field',
		'wpp-reading-time',
		'wpp_rt_main',
		array( 'label_for' => 'wpp_rt_words_per_minute' )
	);
}
add_action( 'admin_init', 'wpp_rt_register_settings' );

/**
 * Sanitize: whole number between 50 and 1000, otherwise fall back to 200.
 */
function wpp_rt_sanitize_wpm( $value ) {
	$value = absint( $value );
	if ( $value < 50 || $value > 1000 ) {
		add_settings_error(
			'wpp_rt_words_per_minute',
			'wpp_rt_invalid',
			__( 'Please enter a number between 50 and 1000.', 'wpp-reading-time' )
		);
		return 200;
	}
	return $value;
}

/**
 * Output the number field (escaped).
 */
function wpp_rt_render_field() {
	$wpm = (int) get_option( 'wpp_rt_words_per_minute', 200 );
	printf(
		'<input type="number" id="wpp_rt_words_per_minute" name="wpp_rt_words_per_minute" value="%s" min="50" max="1000" step="1" class="small-text" />',
		esc_attr( $wpm )
	);
}

/**
 * Add the page under Settings. Only users who can manage options see it.
 */
function wpp_rt_add_settings_page() {
	add_options_page(
		__( 'Reading Time Badge', 'wpp-reading-time' ),
		__( 'Reading Time', 'wpp-reading-time' ),
		'manage_options',
		'wpp-reading-time',
		'wpp_rt_render_settings_page'
	);
}
add_action( 'admin_menu', 'wpp_rt_add_settings_page' );

/**
 * Render the settings page. settings_fields() prints the nonce;
 * options.php verifies it and checks the capability on save.
 */
function wpp_rt_render_settings_page() {
	if ( ! current_user_can( 'manage_options' ) ) {
		return;
	}
	?>
	<div class="wrap">
		<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
		<form action="options.php" method="post">
			<?php
			settings_fields( 'wpp_rt_settings' );
			do_settings_sections( 'wpp-reading-time' );
			submit_button();
			?>
		</form>
	</div>
	<?php
}

/**
 * Prepend the badge to single post content. Filters must always return a value.
 */
function wpp_rt_add_badge( $content ) {
	if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
		return $content;
	}

	$wpm     = max( 50, (int) get_option( 'wpp_rt_words_per_minute', 200 ) );
	$words   = str_word_count( wp_strip_all_tags( $content ) );
	$minutes = max( 1, (int) ceil( $words / $wpm ) );

	/* translators: %s: number of minutes. */
	$label = sprintf( _n( '%s min read', '%s min read', $minutes, 'wpp-reading-time' ), number_format_i18n( $minutes ) );

	return '<p class="wpp-rt-badge">' . esc_html( $label ) . '</p>' . $content;
}
add_filter( 'the_content', 'wpp_rt_add_badge' );

And the cleanup file, wpp-reading-time/uninstall.php:

<?php
// uninstall.php — runs only when the plugin is deleted from the Plugins screen.
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
	exit;
}

delete_option( 'wpp_rt_words_per_minute' );

The word count is an estimate: str_word_count() works best with English text, so a multilingual site may want a smarter counter. That is exactly the kind of limitation a review should surface.

Step 3: The Review Checklist

Never skip this step. Go line by line with the checklist below, and ask the AI to explain anything you do not understand.

  • ABSPATH guard: The file exits if loaded directly, outside WordPress.
  • Sanitize input: register_setting() uses a sanitize callback with absint() and a range check.
  • Escape output: The field value uses esc_attr(), the page title and badge use esc_html().
  • Nonce: settings_fields() outputs a nonce that WordPress verifies when options.php saves the form. Custom forms that do not use the Settings API need their own wp_nonce_field() and check_admin_referer().
  • Capability: The page requires manage_options, and the render function double-checks with current_user_can().
  • Prefix: Every function and option starts with wpp_rt_ to avoid collisions with other plugins.
  • i18n: User-facing strings use __() or _n() with a consistent text domain.
  • Uninstall cleanup: uninstall.php removes the stored option so nothing is left behind.
  • Filters return values: The the_content callback returns $content on every path. Forgetting this would blank out every post.

Professor’s note: You can also ask a second AI session to “review this plugin for WordPress security and coding-standard issues.” It is a useful extra pass, but you are still the one who signs off.

Step 4: Test Safely

  1. Try it in WordPress Playground — Playground runs WordPress entirely in your browser, so you can upload the plugin zip and experiment with zero risk.
  2. Test on a local or staging site — use a local tool or your host’s staging feature with a copy of your real theme and plugins.
  3. Turn on debugging — set WP_DEBUG and WP_DEBUG_LOG to true on staging and watch for notices or warnings.
  4. Try to break it — enter 0, -5, 5000, and letters in the settings field; view pages, archives, and the block editor.
  5. Check tooling — the Plugin Check plugin and PHP_CodeSniffer with WordPress Coding Standards can flag common issues.

Step 5: Ship It

  • Take a full backup of your live site first.
  • Zip the plugin folder and upload it via Plugins → Add New → Upload Plugin.
  • Activate, configure the setting, and check a few posts.
  • Keep the code in version control (Git) so you can track changes and roll back.
  • If you plan to share it publicly, review the WordPress.org plugin guidelines before submitting.

Want more projects like this? Explore our vibe coding hub, brush up on fundamentals in web development, and see broader AI tips in AI + WordPress.

Homework

  • Install this plugin in WordPress Playground and change the words-per-minute setting.
  • Ask an AI assistant to add a toggle for showing the badge on pages too, then run the full review checklist on its answer.
  • Write down one thing the AI got wrong or left out during your session.

Ask the Professor a question

Your email address will not be published. Required fields are marked *