AI text generation in WordPress via API: a plugin with an editor button
A small WordPress plugin: a REST route calls Claude via wp_remote_post, and a panel in the block editor inserts the generated text into the post.
4 min · · development · автоматизация
AI text generation in WordPress via API is handy when authors need a draft paragraph, a product description or an intro option right in the editor. Below is a small plugin: the PHP side calls Claude through Zukko, and a panel in the block editor (Gutenberg) sends the request and inserts the answer into the post. The API key never reaches the browser.
How it works
- The author types a task in the "Text generation" panel in the editor sidebar and clicks the button.
- JavaScript sends a request to the site's REST route
/wp-json/zukko/v1/generateviawp.apiFetch, which adds the current user's nonce automatically. - PHP checks permissions (
edit_posts) and callshttps://api.zukko.pro/v1/chat/completionswithwp_remote_post. - The answer goes back to the editor and is inserted into the post as new blocks.
Why not call the API straight from the browser: then anyone opening developer tools would see the fetch_... key.
The key in wp-config.php
Create a key in the Zukko dashboard and add it to wp-config.php above the "That's all, stop editing!" line:
define( 'ZUKKO_API_KEY', 'fetch_...' );If wp-config.php is in a repository, read the value from the environment:
define( 'ZUKKO_API_KEY', getenv( 'ZUKKO_API_KEY' ) ?: '' );The environment variable is set in PHP-FPM, web server or hosting panel settings, depending on your hosting. It's convenient to give the plugin a separate key with a daily dollar limit, so a bug or an overly active author can't spend more than that.
Server side: REST route and wp_remote_post
Create the folder wp-content/plugins/zukko-generator/ with a zukko-generator.php file:
<?php
/**
* Plugin Name: Zukko Text Generator
* Description: Text generation in the editor via the Zukko API.
* Version: 1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action( 'rest_api_init', function () {
register_rest_route( 'zukko/v1', '/generate', array(
'methods' => 'POST',The key point about wp_remote_post. By default the WordPress HTTP API waits only 5 seconds for a response (the timeout parameter in WP_Http::request), while generating a long text takes longer. That's why timeout is raised to 60. The body is sent as a JSON string via wp_json_encode with an explicit Content-Type: application/json. A network failure comes back as a WP_Error (checked with is_wp_error), while an API error is a normal response with a non-200 code, so the code is checked separately with wp_remote_retrieve_response_code.
sanitize_textarea_field is used instead of sanitize_text_field because the latter strips line breaks, which are often useful in a task.
An editor panel with a button
Next to it, create generator.js. The code is written without a build step (no JSX), so it can be loaded as is:
(function (wp) {
const el = wp.element.createElement;
const { useState } = wp.element;
const { Button, TextareaControl, Notice } = wp.components;
const { useDispatch } = wp.data;
const { PluginDocumentSettingPanel } = wp.editor;
function ZukkoPanel() {
const [prompt, setPrompt] = useState('');
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const { insertBlocks } = useDispatch('core/block-editor');
async function generate() {What happens here:
registerPluginfromwp.pluginsandPluginDocumentSettingPanelfromwp.editoradd a panel to the post settings tab, as described in the Block Editor Handbook. Before WordPress 6.6 this component lived inwp.editPost; on an older version replacewp.editorwithwp.editPost.wp.apiFetchautomatically adds theX-WP-Nonceheader and the current user's cookies, sopermission_callbackon the server knows who is calling the route. No need to look for the nonce on the page by hand.wp.blocks.pasteHandlerturns the answer text into blocks, andinsertBlocksinserts them into the post. If you only need a title or excerpt, useeditPost({ title: data.text })fromuseDispatch('core/editor').
Activate the plugin under "Plugins", open any post, and the panel appears in the right sidebar.
Moderation and control
Generated text is a draft. The plugin publishes nothing on its own: the text lands in the editor and the author proofreads it. That matters both for quality (the model can get facts wrong) and for SEO: pages assembled without review are rarely useful to readers.
What else to add in a production version:
- A request log. Store the user, task, model and answer size (for example via
error_logor a separate table) to see who generates how much. - Tighter permissions. If only editors should generate, replace
edit_postswithedit_others_posts. - Key limits. A daily and monthly dollar limit is set on the key in the dashboard; see key limits.
Cost and model choice
The model is set by the 'model' => 'claude-sonnet-5' line. Zukko prices per 1M tokens (input/output): claude-sonnet-5 and claude-opus-5-5 are $0.30 / $0.30, gpt-5.6-sol $0.30 / $0.30, gpt-5.6-luna $0.10 / $0.10, glm-5.3-flash $0.04 / $0.04. The current list is at GET https://api.zukko.pro/v1/models or on the "Models" page in the dashboard.
Example: a task with the system prompt is 300 input tokens, the answer 2,000 output tokens. On claude-sonnet-5: (300 + 2,000) × $0.30 / 1,000,000 = $0.00069 per text, so a hundred texts cost $0.069. Test which model writes best for your site on your own tasks: give two or three models the same 10 tasks and compare the results blind.
FAQ
Why does the request cut off after a few seconds?
Most likely timeout is missing: by default wp_remote_post waits 5 seconds. Set it to 60 or more. If the web server or PHP (max_execution_time) also cuts the request, raise that too or lower max_tokens.
The panel doesn't show up in the editor
Check that the plugin is active and generator.js loaded (the Network tab in developer tools). The browser console shows an error if a dependency from the wp_enqueue_script list is missing. The panel doesn't work in the classic (non-block) editor.
Can I generate texts in bulk, without the editor?
Yes: the same wp_remote_post function can be called from a WP-CLI command or a WP-Cron task, saving the result with wp_insert_post in draft status. Publish only after review.
What if the key leaks?
Delete it in the dashboard, create a new one and replace the value in wp-config.php. The full key is shown once at creation, so save it right away.
zukko
Pay 25×
less
for Claude Code & ChatGPT
One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.
Related articles
- Translating subtitles with a neural network: SRT without broken timecodesThe main risk when translating SRT with a neural network is a broken file structure. A scheme where timecodes never reach the model, and a Python script with validation.
- Editing texts with a neural network: proofreading, style and cuttingDifferent kinds of editing need different prompts. Wording for proofreading, style and cutting, how to get edits with explanations and how not to lose the author's voice.
- A neural network for writing scripts: Bash, PowerShell and PythonA model writes scripts for routine fast, but running them blindly is risky. How to state the task, demand a safe dry-run mode and check the result before launch.
- Dify in Docker: installation, first-run setup and connecting modelsInstall Dify with Docker Compose, create the admin account and connect Claude, GPT and DeepSeek models on one key. Plus what to do when containers will not start.