Integrating AI APIs like OpenAI’s GPT‑4o into your WordPress site can dramatically boost your content creation process.
In this guide, we’ll build a custom WordPress plugin that does just that – generating post content from within the editor with a single click.
This updated tutorial uses the GPT‑4o chat API to output Markdown, converts it into Gutenberg blocks (or classic editor content), and includes a sleek meta box with a loading spinner.
Let’s dive in.
1. Set Up Your Development Environment
Before writing any code, make sure you have a local development environment ready:
- Install Local WordPress:
Use tools like Local or XAMPP to create a local WordPress installation for testing your plugin. - Choose Your Code Editor:
Use a modern code editor such as Visual Studio Code or PHPStorm. Ensure you have extensions for PHP, JavaScript, and Markdown. - Optional – Composer:
If your plugin will use external libraries, install Composer for dependency management.
2. Create the Plugin Structure
Every WordPress plugin starts with a basic file structure. Follow these steps:
2.1 Create the Plugin Folder
In your local WordPress installation, navigate to
wp-content/plugins/
and create a folder named, for example,
ai-powered-plugin
.
2.2 Add the Main Plugin File
Inside the folder, create a file called
ai-powered-plugin.php
and add the following header:
<?php
/**
* Plugin Name: AI-Powered Plugin
* Description: A custom WordPress plugin that integrates AI APIs (like GPT‑4o) to generate post content via a button in the editor.
* Version: 1.0.0
* Author: Your Name
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
// [Code continues below...]
3. Integrate the AI API
We’ll use OpenAI’s GPT‑4o chat API to generate content. Our plugin will:
- Call the GPT‑4o chat endpoint with a custom prompt (which instructs the API to output Markdown).
- Increase the token limit to allow for longer content.
- Convert the returned Markdown into HTML and then into Gutenberg blocks (or simply insert it into the Classic Editor).
3.1 API Request and Response Handling
Below is the complete PHP code for handling the API request:
/**
* Generate content using the GPT‑4o chat API.
*/
function ai_generate_content_via_api( $prompt ) {
// Retrieve and sanitize the API key.
$api_key = sanitize_text_field( get_option( 'ai_plugin_api_key' ) );
if ( empty( $api_key ) ) {
return 'API key not set. Please configure the API key in the AI Plugin Settings.';
}
// Use the chat completions endpoint.
$endpoint = 'https://api.openai.com/v1/chat/completions';
// Append instructions so the response is in Markdown format.
$full_prompt = $prompt . "\n\nPlease respond in Markdown format using proper headings, bullet points, and other Markdown conventions. Do not wrap the answer in a code block.";
// Prepare the API request with an increased timeout.
$response = wp_remote_post( $endpoint, array(
'timeout' => 30, // Timeout set to 30 seconds.
'headers' => array(
'Authorization' => 'Bearer ' . $api_key,
'Content-Type' => 'application/json',
),
'body' => json_encode( array(
'model' => 'gpt-4o', // Adjust the model if needed.
'messages' => array(
array(
'role' => 'user',
'content' => $full_prompt,
),
),
'max_tokens' => 2048, // Increased token size.
) ),
) );
if ( is_wp_error( $response ) ) {
return 'Error: ' . $response->get_error_message();
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
// Check if the API returned the expected structure.
if ( ! isset( $body['choices'] ) || ! is_array( $body['choices'] ) || empty( $body['choices'] ) ) {
$error_message = isset( $body['error']['message'] )
? $body['error']['message']
: 'Unexpected API response: ' . print_r( $body, true );
return 'Error: ' . $error_message;
}
// For chat completions, the generated text is in the "message" object.
$generated_text = isset( $body['choices'][0]['message']['content'] )
? trim( $body['choices'][0]['message']['content'] )
: '';
return $generated_text;
}
3.2 AJAX Endpoint
To make the API call without reloading the page, create an AJAX handler:
/**
* AJAX handler for generating AI content.
*/
function ai_ajax_generate_content() {
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( 'Unauthorized' );
}
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'ai_generate_content_nonce' ) ) {
wp_send_json_error( 'Invalid nonce' );
}
$prompt = isset( $_POST['prompt'] ) ? sanitize_text_field( $_POST['prompt'] ) : '';
if ( empty( $prompt ) ) {
wp_send_json_error( 'No prompt provided' );
}
$generated = ai_generate_content_via_api( $prompt );
if ( empty( $generated ) || strpos( $generated, 'Error:' ) === 0 ) {
wp_send_json_error( $generated );
}
wp_send_json_success( array( 'content' => $generated ) );
}
add_action( 'wp_ajax_generate_ai_content', 'ai_ajax_generate_content' );
4. Build the Editor Meta Box
The plugin adds a meta box to the post editor with a text field for the prompt and a “Generate AI Content” button. When clicked, the button is disabled and shows a spinner to indicate loading.
4.1 Meta Box Markup
/**
* Add meta box to the post editing screen.
*/
function ai_add_meta_box() {
add_meta_box(
'ai_content_generator',
'AI Content Generator',
'ai_meta_box_callback',
'post',
'side',
'default'
);
}
add_action( 'add_meta_boxes', 'ai_add_meta_box' );
/**
* Render the meta box.
*/
function ai_meta_box_callback( $post ) {
// Use nonce for security.
wp_nonce_field( 'ai_generate_content_nonce', 'ai_generate_content_nonce_field' );
// Set a default prompt based on the post title.
$default_prompt = 'Write a blog post about ' . $post->post_title;
$prompt = get_post_meta( $post->ID, '_ai_generate_prompt', true );
if ( empty( $prompt ) ) {
$prompt = $default_prompt;
}
?>
<p>
<label for="ai_generate_prompt"><strong>Prompt:</strong></label>
<input type="text" id="ai_generate_prompt" name="ai_generate_prompt" value="<?php echo esc_attr( $prompt ); ?>" style="width:100%;" />
</p>
<p>
<button type="button" id="ai_generate_button" class="button button-primary">Generate AI Content</button>
</p>
<p id="ai_generate_status"></p>
<?php
}
5. Enqueue Scripts and Styles
We need to load our JavaScript (which uses Marked.js to convert Markdown) and some CSS for the spinner.
/**
* Enqueue admin scripts for our meta box.
*/
function ai_plugin_enqueue_admin_scripts( $hook ) {
// Only enqueue on post editing screens.
if ( 'post.php' != $hook && 'post-new.php' != $hook ) {
return;
}
// Enqueue Marked.js for Markdown parsing.
wp_enqueue_script( 'marked', 'https://cdn.jsdelivr.net/npm/marked/marked.min.js', array(), '4.0.12', true );
wp_enqueue_script( 'ai-plugin-script', plugin_dir_url( __FILE__ ) . 'ai-plugin.js', array( 'jquery', 'marked' ), '1.0', true );
wp_localize_script( 'ai-plugin-script', 'aiPlugin', array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'ai_generate_content_nonce' ),
) );
}
add_action( 'admin_enqueue_scripts', 'ai_plugin_enqueue_admin_scripts' );
Note: We include a small inline CSS (or you can add it via a dedicated admin stylesheet) to animate the spinner:
function ai_plugin_api_key_callback() {
$api_key = get_option( 'ai_plugin_api_key' );
echo '<input type="text" name="ai_plugin_api_key" value="' . esc_attr( $api_key ) . '" class="regular-text">';
echo '<style>
.spin { animation: spin 2s infinite linear; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
</style>';
}
6. API Key Management and Plugin Settings
Create a settings page where you can securely store your API key:
/**
* Plugin Settings Page for API Key management.
*/
function ai_plugin_settings_page() {
?>
<div class="wrap">
<h1>AI Plugin Settings</h1>
<form method="post" action="options.php">
<?php
settings_fields( 'ai_plugin_options_group' );
do_settings_sections( 'ai-plugin' );
submit_button();
?>
</form>
</div>
<?php
}
function ai_plugin_register_settings() {
register_setting( 'ai_plugin_options_group', 'ai_plugin_api_key' );
add_settings_section( 'ai_plugin_main_section', 'API Settings', null, 'ai-plugin' );
add_settings_field( 'ai_plugin_api_key', 'API Key', 'ai_plugin_api_key_callback', 'ai-plugin', 'ai_plugin_main_section' );
}
add_action( 'admin_init', 'ai_plugin_register_settings' );
function ai_plugin_add_admin_menu() {
add_menu_page( 'AI Plugin Settings', 'AI Plugin', 'manage_options', 'ai-plugin', 'ai_plugin_settings_page' );
}
add_action( 'admin_menu', 'ai_plugin_add_admin_menu' );
7. The JavaScript File (ai-plugin.js)
This file handles the button click, disables it during processing, shows a spinner, and–once the content is generated–converts the Markdown output to HTML and then to Gutenberg blocks.
jQuery(document).ready(function($) {
$('#ai_generate_button').on('click', function(e) {
e.preventDefault();
var $btn = $('#ai_generate_button');
// Store the original button content to restore later.
var originalText = $btn.html();
// Disable the button and change its content to show a spinner.
$btn.prop('disabled', true).html('<span class="dashicons dashicons-update spin"></span> Generating...');
var prompt = $('#ai_generate_prompt').val();
$('#ai_generate_status').text('Generating content, please wait...');
$.ajax({
url: aiPlugin.ajaxurl,
method: 'POST',
data: {
action: 'generate_ai_content',
nonce: aiPlugin.nonce,
prompt: prompt
},
success: function(response) {
if (response.success) {
var generatedContent = response.data.content;
// For Classic Editor: update the textarea.
if ($('#content').length) {
$('#content').val(generatedContent);
}
// For Gutenberg:
else if ( typeof wp !== 'undefined' && wp.data && wp.data.dispatch ) {
// Convert the Markdown text to HTML.
var htmlContent = marked.parse(generatedContent);
var blocks = null;
// Try using the rawHandler if available.
if ( typeof wp.blocks.rawHandler === 'function' ) {
blocks = wp.blocks.rawHandler({ HTML: htmlContent });
}
// Fallback to the unstable parse method.
else if ( typeof wp.blocks.__unstableParse === 'function' ) {
blocks = wp.blocks.__unstableParse(htmlContent);
}
// Last resort, use wp.blocks.parse.
else if ( typeof wp.blocks.parse === 'function' ) {
blocks = wp.blocks.parse(htmlContent);
}
if ( blocks && blocks.length > 0 ) {
// Replace the current blocks with the parsed blocks.
wp.data.dispatch('core/block-editor').resetBlocks(blocks);
} else {
// If no blocks were generated, insert as a paragraph block.
wp.data.dispatch('core/block-editor').insertBlocks(
wp.blocks.createBlock('core/paragraph', { content: htmlContent })
);
}
}
$('#ai_generate_status').text('Content generated successfully.');
} else {
$('#ai_generate_status').text('Error: ' + response.data);
}
},
error: function(xhr, status, error) {
$('#ai_generate_status').text('AJAX error: ' + error);
},
complete: function() {
// Re-enable the button and restore its original content.
$btn.prop('disabled', false).html(originalText);
}
});
});
});
8. Test and Deploy Your Plugin
- Activate the Plugin:
Compress yourai-powered-plugin
folder into a ZIP file, then upload and activate it from your WordPress admin. - Configure API Key:
Go to the AI Plugin settings page and enter your OpenAI API key. - Generate Content:
In the post editor (Classic or Gutenberg), adjust the prompt if needed, then click Generate AI Content. Notice how the button disables and shows a spinner during processing. Once complete, the generated Markdown is converted into proper blocks (or inserted into the classic editor). - Debug and Optimize:
Use tools like Query Monitor or error_log() if you encounter any issues. Make sure to follow WordPress coding standards for maintainability and security.
Final Thoughts
Building a custom WordPress plugin that leverages AI APIs is challenging–but incredibly rewarding. By following this updated guide, you now have a scalable solution that integrates GPT‑4o, converts Markdown to Gutenberg blocks, and offers a polished user experience with loading indicators.
Stay disciplined, test rigorously, and keep refining your plugin. Now, go execute and harness the power of AI on your WordPress site!
Leave a Reply