The Title Generation experiment adds AI-powered title suggestions to the WordPress post editor. It exposes a Generate / Regenerate button next to the post title and opens a modal where the suggestion can be edited before insertion. The experiment registers a WordPress Ability (ai/title-generation) that can be called both through the in-editor UI and directly via REST API. It is also the first built-in ability that opts into the editorial Guidelines service (site, copy categories), so per-site tone and brand guidance is automatically injected into every title prompt.
When enabled, the Title Generation experiment surfaces a button next to the post title field that reads Generate when the title is empty and Regenerate when it already has text. Clicking it sends the post content to the AI and opens a modal showing the suggestion in an editable text area. Users can:
- Edit the suggested title inline before inserting.
- Regenerate to replace the current suggestion with a new one (without closing the modal).
- Insert to write the suggestion into the post title field.
Key Features:
- One-click title generation from the current post content
- Suggestions are constrained to ≤ 80 characters, plain text, no markdown or quotes
- Editable suggestion before insertion
- Regenerate without losing the modal context
- Works for any post type that supports titles
- Integrates with site Editorial Guidelines (
site,copycategories) when configured
The experiment consists of two main components:
- Experiment Class (
WordPress\AI\Experiments\Title_Generation\Title_Generation): handles registration, asset enqueuing, and UI integration. - Ability Class (
WordPress\AI\Abilities\Title_Generation\Title_Generation): implements the title generation logic via the WordPress Abilities API.
The ability can be called directly via REST API for automation, bulk back-fills, or custom UI integrations.
array(
'type' => 'object',
'properties' => array(
'content' => array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'description' => 'Content to generate title suggestions for.',
),
'context' => array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'description' => 'Additional context. Either a string of additional context, or a post ID (as a string) — when a numeric ID is supplied, the post content is fetched and used.',
),
),
)array(
'type' => 'object',
'properties' => array(
'title' => array(
'type' => 'string',
'description' => 'Generated title suggestion.',
),
),
)-
If
contextis a numeric post ID:- Verifies the post exists; returns
post_not_foundotherwise. - Checks
current_user_can( 'edit_post', $post_id ). - Verifies the post type has
show_in_restenabled — otherwise the callback returnsfalse.
- Verifies the post exists; returns
-
If
contextis not a post ID:- Checks
current_user_can( 'edit_posts' ).
- Checks
Title_Generation declares:
protected function guideline_categories(): array {
return array( 'site', 'copy' );
}When the wp_guideline CPT is registered (Gutenberg ≥ 23.0) and the corresponding _guideline_site / _guideline_copy post-meta values are set, Abstract_Ability::get_system_instruction() automatically prepends them to the title-generation system prompt as <guidelines><site-context>...</site-context><copy-guidelines>...</copy-guidelines></guidelines>. See Editorial Guidelines in the Developer Guide for the shared Guidelines surface.
POST /wp-json/wp-abilities/v1/abilities/ai/title-generation/run
You can authenticate using either:
- Application Password (Recommended)
- Cookie Authentication with Nonce
See TESTING_REST_API.md for detailed authentication instructions.
curl -X POST "https://yoursite.com/wp-json/wp-abilities/v1/abilities/ai/title-generation/run" \
-u "username:application-password" \
-H "Content-Type: application/json" \
-d '{
"input": {
"context": "123"
}
}'Response:
{ "title": "How Renewable Energy Is Reshaping the Modern Grid" }curl -X POST "https://yoursite.com/wp-json/wp-abilities/v1/abilities/ai/title-generation/run" \
-u "username:application-password" \
-H "Content-Type: application/json" \
-d '{
"input": {
"content": "This article walks through how distributed teams can adopt asynchronous decision-making practices to reduce meeting load while preserving alignment."
}
}'curl -X POST "https://yoursite.com/wp-json/wp-abilities/v1/abilities/ai/title-generation/run" \
-u "username:application-password" \
-H "Content-Type: application/json" \
-d '{
"input": {
"content": "Detailed walkthrough of the new caching layer.",
"context": "Audience: senior backend engineers; tone: technical, no marketing fluff."
}
}'import { runAbility } from '../../utils/run-ability';
const { title } = await runAbility< { title: string } >(
'ai/title-generation',
{
context: String( postId ),
content: editedContent,
}
);post_not_found—contextwas a numeric post ID but no such post exists.content_not_provided— Neithercontentnor a usable postcontextproduced any text.insufficient_capabilities— Caller lacksedit_post(with a post ID) oredit_posts(without).no_results— The AI client did not return any text.- A WP_Error from
ensure_text_generation_supported()if no connected provider supports text generation.
Example:
{
"code": "content_not_provided",
"message": "Content is required to generate title suggestions.",
"data": { "status": 400 }
}Edit includes/Abilities/Title_Generation/system-instruction.php to change the length cap, tone constraints, or output requirements.
For per-site tweaks without forking, register a wpai_system_instruction filter:
add_filter( 'wpai_system_instruction', function ( string $instruction, string $name ): string {
if ( 'ai/title-generation' !== $name ) {
return $instruction;
}
return $instruction . "\nAvoid all-caps and exclamation marks.";
}, 10, 2 );Because the ability declares guideline_categories(): ['site', 'copy'], populating the _guideline_site and _guideline_copy post-meta on the latest wp_guideline post is enough to reshape every title generation prompt site-wide. Use wpai_max_guideline_length to cap how much of each category gets injected (default 5000 characters), and wpai_use_guidelines (__return_false) to disable injection on staging.
The ability uses WordPress\AI\get_preferred_models_for_text_generation(). Override the cross-cutting wpai_preferred_text_models filter:
add_filter( 'wpai_preferred_text_models', function ( array $models ): array {
return array(
array( 'anthropic', 'claude-sonnet-4-6' ),
array( 'openai', 'gpt-5.4-mini' ),
);
} );Content passed to the ability is run through normalize_content() (HTML stripped, shortcodes removed, etc.). Filter the result:
add_filter( 'wpai_pre_normalize_content', function ( string $content ): string {
// Pre-normalization tweak.
return $content;
} );
add_filter( 'wpai_normalize_content', function ( string $content ): string {
// Post-normalization tweak.
return $content;
} );When the caller supplies a post ID, get_post_context() (includes/helpers.php) gathers post details and terms via the ai/get-post-details and ai/get-post-terms utility abilities. To shape that context, hook the corresponding filters:
add_filter( 'wpai_get_post_details', function ( array $details, int $post_id, array $fields ): array {
// Drop the slug, append computed reading time, etc.
return $details;
}, 10, 3 );-
Enable the experiment:
- Go to
Settings → AI - Toggle Title Generation to enabled
- Ensure you have valid AI credentials configured
- Go to
-
Test in normal editing mode (classic post editor):
- Create or edit a post; focus the title input
- Verify the floating toolbar appears above the title field
- With an empty title, click the button — it should read Generate
- Verify the modal opens with a suggestion in an editable text area
- Edit the suggestion, click Insert, verify the title updates
- With a non-empty title, focus the title and click again — the button should read Regenerate
- Open the modal and click Regenerate inside it — verify a new suggestion appears without closing the modal
-
Test in block mode (template/site editor):
- Edit a template that contains a
core/post-titleblock - Select the block and verify the toolbar appears in
BlockControls - Generate, edit, and insert a suggestion as above
- Edit a template that contains a
-
Test with a non-title post type:
- Edit a post type that does not declare
titlesupport - Verify no toolbar / button appears (the asset enqueue is skipped)
- Edit a post type that does not declare
-
Test guideline injection:
- With a populated
wp_guidelinepost (_guideline_site,_guideline_copy), verify generated titles reflect the configured tone - Set
add_filter( 'wpai_use_guidelines', '__return_false' )and re-test — guidelines should no longer affect output
- With a populated
-
Test REST API:
- Use curl or Postman to test the REST endpoint
- Verify authentication works
- Test with a valid post ID, with freeform content, and with both
- Verify error handling for invalid inputs (
post_not_found,content_not_provided)
- Requires valid AI credentials.
- Only runs on
post.phpandpost-new.phpadmin screens. - Only attaches when the post type supports
titleand is not an attachment. - Users must have
edit_post(with apost_idcontext) oredit_posts(without).
- Content is normalized before being sent to the AI (HTML stripped, shortcodes removed).
- When a post ID is supplied as
context, the post's title and assigned terms are appended as<additional-context>to help the model produce a more relevant suggestion.
- The ability uses
get_preferred_models_for_text_generation()to pick a model. - Models are tried in order until one succeeds.
- Temperature is set to 0.7 for natural variation across regenerations.
The system instruction guides the model to:
- Produce titles ≤ 80 characters.
- Output plain text only — no markdown, bullets, numbering, quotes, or code fences.
- Match the language of the input.
- Reflect the actual content rather than producing generic clickbait.
- Respond with only the title — no preamble, no closing remarks.
When the Editorial Guidelines service is configured, <site-context> and <copy-guidelines> blocks are prepended via Abstract_Ability::get_system_instruction().
- One title per request — no batch endpoint.
- The
TitleToolbarWrapperreaches into the editor iframe via DOM querying and aMutationObserver. It is resilient to late-loading editors but assumes the standardeditor-canvas/wp-block-editor-iframe__iframemarkup; heavily customized editors may need additional selectors. - Suggestions are generated in real time and not cached.
- The output of the ability is sanitized with
sanitize_text_field()and stripped of surrounding"/'characters; titles that legitimately need leading/trailing quotes will lose them.