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

  1. The author types a task in the "Text generation" panel in the editor sidebar and clicks the button.
  2. JavaScript sends a request to the site's REST route /wp-json/zukko/v1/generate via wp.apiFetch, which adds the current user's nonce automatically.
  3. PHP checks permissions (edit_posts) and calls https://api.zukko.pro/v1/chat/completions with wp_remote_post.
  4. 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:

bash
define( 'ZUKKO_API_KEY', 'fetch_...' );

If wp-config.php is in a repository, read the value from the environment:

bash
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:

bash
<?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:

bash
(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:

  • registerPlugin from wp.plugins and PluginDocumentSettingPanel from wp.editor add a panel to the post settings tab, as described in the Block Editor Handbook. Before WordPress 6.6 this component lived in wp.editPost; on an older version replace wp.editor with wp.editPost.
  • wp.apiFetch automatically adds the X-WP-Nonce header and the current user's cookies, so permission_callback on the server knows who is calling the route. No need to look for the nonce on the page by hand.
  • wp.blocks.pasteHandler turns the answer text into blocks, and insertBlocks inserts them into the post. If you only need a title or excerpt, use editPost({ title: data.text }) from useDispatch('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_log or a separate table) to see who generates how much.
  • Tighter permissions. If only editors should generate, replace edit_posts with edit_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.

Get API keyDocumentation

api.zukko.pro · fetch_*

AI text generation in WordPress via API · Zukko