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/clientStep 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
- Copy the plugin folder to
wp-content/plugins/wp-ai-assistant/ - Run
composer installinside the plugin folder - Activate the plugin in WordPress Admin → Plugins
- Go to Settings → AI Assistant and enter your OpenAI API key
- 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.