How to Build a WordPress AI Plugin (Step-by-Step Guide 2026)

Building a WordPress plugin that uses AI opens up a wide range of features: content generation, SEO suggestions, AI chat widgets, image descriptions, and more. This guide builds a complete plugin from scratch — a post content assistant that uses the OpenAI API to generate or improve text, accessible from the WordPress block editor.

For the OpenAI PHP SDK used in this plugin, see OpenAI PHP SDK Guide.


Plugin Structure

wp-ai-assistant/
├── wp-ai-assistant.php     # Main plugin file (headers + init)
├── includes/
│   ├── class-api.php       # OpenAI API wrapper
│   └── class-ajax.php      # AJAX handlers
├── assets/
│   ├── editor.js           # Block editor sidebar panel
│   └── editor.css          # Styles
└── composer.json           # For openai-php/client

Step 1: Main Plugin File

<?php
/**
 * Plugin Name: WP AI Assistant
 * Description: AI-powered content assistant using OpenAI.
 * Version:     1.0.0
 * Author:      Your Name
 * Requires PHP: 8.1
 */

defined('ABSPATH') || exit;

define('WP_AI_ASSISTANT_DIR', plugin_dir_path(__FILE__));
define('WP_AI_ASSISTANT_URL', plugin_dir_url(__FILE__));

// Auto-load Composer dependencies
if (file_exists(WP_AI_ASSISTANT_DIR . 'vendor/autoload.php')) {
    require_once WP_AI_ASSISTANT_DIR . 'vendor/autoload.php';
}

require_once WP_AI_ASSISTANT_DIR . 'includes/class-api.php';
require_once WP_AI_ASSISTANT_DIR . 'includes/class-ajax.php';

// Register settings page
add_action('admin_menu', function () {
    add_options_page(
        'WP AI Assistant',
        'AI Assistant',
        'manage_options',
        'wp-ai-assistant',
        'wp_ai_assistant_settings_page'
    );
});

function wp_ai_assistant_settings_page(): void
{
    if (isset($_POST['wp_ai_key_nonce']) && wp_verify_nonce($_POST['wp_ai_key_nonce'], 'save_ai_key')) {
        $key = sanitize_text_field($_POST['openai_api_key'] ?? '');
        update_option('wp_ai_assistant_api_key', $key);
        echo '<div class="notice notice-success"><p>Settings saved.</p></div>';
    }
    $key = get_option('wp_ai_assistant_api_key', '');
    ?>
    <div class="wrap">
        <h1>WP AI Assistant Settings</h1>
        <form method="post">
            <?php wp_nonce_field('save_ai_key', 'wp_ai_key_nonce'); ?>
            <table class="form-table">
                <tr>
                    <th>OpenAI API Key</th>
                    <td><input type="password" name="openai_api_key"
                         value="<?= esc_attr($key); ?>" class="regular-text"></td>
                </tr>
            </table>
            <?php submit_button('Save Settings'); ?>
        </form>
    </div>
    <?php
}

Step 2: OpenAI API Wrapper

<?php
// includes/class-api.php

class WP_AI_Assistant_API
{
    private string $apiKey;

    public function __construct()
    {
        $this->apiKey = get_option('wp_ai_assistant_api_key', '');
    }

    public function generate(string $prompt, int $maxTokens = 512): string
    {
        if (empty($this->apiKey)) {
            throw new RuntimeException('API key not configured. Go to Settings → AI Assistant.');
        }

        $client = OpenAI::client($this->apiKey);

        $response = $client->chat()->create([
            'model'      => 'gpt-4o-mini',
            'messages'   => [
                ['role' => 'system', 'content' => 'You are a professional content writer. Write clear, engaging content.'],
                ['role' => 'user',   'content' => $prompt],
            ],
            'max_tokens' => $maxTokens,
        ]);

        return $response->choices[0]->message->content;
    }

    public function improve(string $text, string $instruction): string
    {
        return $this->generate(
            "Improve the following text according to this instruction: {$instruction}\n\nText:\n{$text}",
            1024
        );
    }
}

Step 3: AJAX Handlers

<?php
// includes/class-ajax.php

class WP_AI_Assistant_Ajax
{
    public function __construct()
    {
        add_action('wp_ajax_wp_ai_generate', [$this, 'handle_generate']);
        add_action('wp_ajax_wp_ai_improve',  [$this, 'handle_improve']);
    }

    public function handle_generate(): void
    {
        check_ajax_referer('wp_ai_assistant_nonce', 'nonce');

        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Insufficient permissions', 403);
        }

        $prompt = sanitize_textarea_field($_POST['prompt'] ?? '');
        if (empty($prompt)) {
            wp_send_json_error('Prompt is required');
        }

        try {
            $api    = new WP_AI_Assistant_API();
            $result = $api->generate($prompt);
            wp_send_json_success(['content' => $result]);
        } catch (Exception $e) {
            wp_send_json_error($e->getMessage());
        }
    }

    public function handle_improve(): void
    {
        check_ajax_referer('wp_ai_assistant_nonce', 'nonce');

        if (!current_user_can('edit_posts')) {
            wp_send_json_error('Insufficient permissions', 403);
        }

        $text        = sanitize_textarea_field($_POST['text']        ?? '');
        $instruction = sanitize_text_field($_POST['instruction'] ?? 'Make it more engaging');

        try {
            $api    = new WP_AI_Assistant_API();
            $result = $api->improve($text, $instruction);
            wp_send_json_success(['content' => $result]);
        } catch (Exception $e) {
            wp_send_json_error($e->getMessage());
        }
    }
}

new WP_AI_Assistant_Ajax();

Step 4: Enqueue Assets

Add this to the main plugin file to load scripts in the block editor:

add_action('enqueue_block_editor_assets', function () {
    wp_enqueue_script(
        'wp-ai-assistant-editor',
        WP_AI_ASSISTANT_URL . 'assets/editor.js',
        ['wp-plugins', 'wp-edit-post', 'wp-element', 'wp-components', 'wp-data', 'wp-api-fetch'],
        '1.0.0',
        true
    );

    wp_localize_script('wp-ai-assistant-editor', 'wpAiAssistant', [
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'nonce'   => wp_create_nonce('wp_ai_assistant_nonce'),
    ]);

    wp_enqueue_style(
        'wp-ai-assistant-editor',
        WP_AI_ASSISTANT_URL . 'assets/editor.css',
        [],
        '1.0.0'
    );
});

Step 5: Block Editor Sidebar Panel

Create assets/editor.js — a sidebar panel in the Gutenberg editor:

const { registerPlugin } = wp.plugins;
const { PluginSidebar, PluginSidebarMoreMenuItem } = wp.editPost;
const { PanelBody, TextareaControl, Button, Notice, Spinner } = wp.components;
const { useState } = wp.element;
const { dispatch } = wp.data;

function AIAssistantPanel() {
    const [prompt, setPrompt]     = useState('');
    const [result, setResult]     = useState('');
    const [loading, setLoading]   = useState(false);
    const [error, setError]       = useState('');

    async function generate() {
        if (!prompt.trim()) return;
        setLoading(true); setError(''); setResult('');

        const data = new FormData();
        data.append('action', 'wp_ai_generate');
        data.append('nonce',  wpAiAssistant.nonce);
        data.append('prompt', prompt);

        try {
            const res  = await fetch(wpAiAssistant.ajaxUrl, { method: 'POST', body: data });
            const json = await res.json();

            if (json.success) {
                setResult(json.data.content);
            } else {
                setError(json.data || 'An error occurred');
            }
        } catch (e) {
            setError('Network error: ' + e.message);
        } finally {
            setLoading(false);
        }
    }

    function insertContent() {
        dispatch('core/block-editor').insertBlocks(
            wp.blocks.createBlock('core/paragraph', { content: result })
        );
        setResult('');
    }

    return wp.element.createElement(
        PluginSidebar,
        { name: 'wp-ai-assistant', title: 'AI Assistant', icon: 'superhero' },
        wp.element.createElement(PanelBody, { title: 'Generate Content', initialOpen: true },
            error && wp.element.createElement(Notice, { status: 'error', isDismissible: false }, error),
            wp.element.createElement(TextareaControl, {
                label: 'What should I write?',
                value: prompt,
                onChange: setPrompt,
                rows: 4,
                placeholder: 'Write a paragraph about the benefits of vector databases...',
            }),
            wp.element.createElement(Button, {
                variant: 'primary',
                onClick: generate,
                disabled: loading || !prompt.trim(),
                isBusy: loading,
            }, loading ? 'Generating...' : 'Generate'),
            result && wp.element.createElement('div', { style: { marginTop: 12 } },
                wp.element.createElement('p', { style: { fontSize: 13 } }, result),
                wp.element.createElement(Button, { variant: 'secondary', onClick: insertContent },
                    'Insert into Editor')
            )
        )
    );
}

registerPlugin('wp-ai-assistant', { render: AIAssistantPanel });

Installing and Activating

  1. Copy the plugin folder to wp-content/plugins/wp-ai-assistant/
  2. Run composer install inside the plugin folder
  3. Activate the plugin in WordPress Admin → Plugins
  4. Go to Settings → AI Assistant and enter your OpenAI API key
  5. Open any post in the block editor — find “AI Assistant” in the top-right sidebar

Security Checklist

  • Always use check_ajax_referer() to validate nonces on AJAX actions
  • Check current_user_can('edit_posts') before processing any request
  • Sanitize all inputs with sanitize_text_field() / sanitize_textarea_field()
  • Escape all output with esc_html() / esc_attr()
  • Store the API key in wp_options, never in code or version control

Summary

  • Plugin header + settings page → store API key in wp_options
  • AJAX handlers → wp_ajax_{action} hooks with nonce + capability checks
  • Block editor integration → enqueue_block_editor_assets + registerPlugin
  • OpenAI calls → wrap in a class, catch exceptions, return clean error messages
  • The same pattern works for any AI feature: SEO suggestions, alt text, summaries

Subscribe to my newsletter — practical guides on Claude API, AI agents, RAG, and automation.

Subscribe