The AI Editorial Notes experiment adds a block-by-block AI editorial review to the WordPress post editor. Clicking "Generate Editorial Notes" in the post sidebar triggers the AI to examine each reviewable block and create WordPress Notes directly on the relevant blocks with concise, actionable suggestions across four categories: Accessibility, Readability, Grammar, and SEO.
When enabled, a "Generate Editorial Notes" button appears in the post status info panel (the sidebar area below the post status). Clicking it triggers a review pass:
- The button label updates to show review progress (
Reviewing blocks… (2 of 8)) - Each content block is sent individually to the AI for analysis
- Notes with suggestions appear directly on the blocks inside the Notes panel
- After completion, a count of new suggestions is shown beneath the button
Key Features:
- Block-level Notes with suggestions scoped to each block's content and type
- Four review categories: Accessibility, Readability, Grammar, SEO
- Accumulating history: subsequent review runs append replies to existing Note threads rather than creating duplicate threads
- Prior suggestions are sent back to the AI as context so it avoids repeating itself
- Blocks whose Note thread has been resolved (marked as approved) are skipped on re-run
- Works with common block types: paragraphs, headings, images, lists, tables, quotes, and more
const REVIEWABLE_BLOCK_TYPES = [
'core/paragraph',
'core/heading',
'core/list',
'core/list-item',
'core/quote',
'core/verse',
'core/image',
'core/table',
'core/preformatted',
'core/pullquote',
];Blocks with fewer than 20 characters of text content are skipped. The review is capped at 25 blocks per run to control cost.
array(
'block_type' => array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'description' => 'The block type, e.g. core/paragraph, core/heading.',
),
'block_content' => array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'description' => 'The plain-text content of the block to review.',
),
'context' => array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'description' => 'Surrounding content to improve review relevance.',
),
'post_id' => array(
'type' => 'integer',
'sanitize_callback' => 'absint',
'description' => 'ID of the post being reviewed.',
),
'existing_notes' => array(
'type' => 'array',
'items' => array( 'type' => 'string' ),
'description' => 'Existing Note texts for this block from prior review runs, used to avoid repeating suggestions.',
),
'review_types' => array(
'type' => 'array',
'items' => array( 'type' => 'string', 'enum' => array( 'accessibility', 'readability', 'grammar', 'seo' ) ),
'description' => 'Review types to perform.',
),
)array(
'type' => 'object',
'properties' => array(
'suggestions' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'review_type' => array( 'type' => 'string' ),
'text' => array( 'type' => 'string' ),
),
),
),
),
)The ability's permission_callback has two paths:
- With a numeric
post_id(post ID): Validates that the post exists, the current user hasedit_postcapability for that specific post, and the post type is registered withshow_in_rest => true. Returnsfalseif the post type is not REST-accessible. - Without a post ID: Requires
current_user_can( 'edit_posts' ).
In both cases, users without the required capability receive an insufficient_capabilities WP_Error.
Notes are WP_Comment objects with comment_type = 'note' and status = 'hold'. Block association is maintained via block metadata:
- New thread:
POST /wp/v2/commentswithparent: 0→ responseidstored inblock.attributes.metadata.noteIdviaupdateBlockAttributes - Reply:
POST /wp/v2/commentswithparent: existingNoteId→ block metadata unchanged (association already set) - AI author: All Notes created by this experiment include
meta: { ai_note: true }. Therest_pre_insert_commentfilter intercepts this and sets the author to "WordPress AI" with no email, URL, or user ID, so Notes are not attributed to the authenticated user's account. - Resolved Notes: Notes with
status = 'approve'(resolved) cause their associated block to be skipped entirely on the next review run.
POST /wp-json/wp-abilities/v1/abilities/ai/editorial-notes/run
See TESTING_REST_API.md for authentication details (application passwords or cookie + nonce).
curl -X POST "https://yoursite.com/wp-json/wp-abilities/v1/abilities/ai/editorial-notes/run" \
-u "username:application-password" \
-H "Content-Type: application/json" \
-d '{
"input": {
"block_type": "core/paragraph",
"block_content": "The committee was formed by the director in order to study the problem and make recommendations.",
"review_types": ["readability", "grammar"],
"existing_notes": [],
"post_id": 42
}
}'Response:
{
"suggestions": [
{
"review_type": "readability",
"text": "Rewrite in active voice: \"The director formed a committee to study the problem and make recommendations.\""
}
]
}curl -X POST "https://yoursite.com/wp-json/wp-abilities/v1/abilities/ai/editorial-notes/run" \
-u "username:application-password" \
-H "Content-Type: application/json" \
-d '{
"input": {
"block_type": "core/image",
"block_content": "",
"review_types": ["accessibility"],
"existing_notes": []
}
}'Response (missing alt text):
{
"suggestions": [
{
"review_type": "accessibility",
"text": "Add descriptive alt text to this image so screen reader users understand its content."
}
]
}import apiFetch from '@wordpress/api-fetch';
async function reviewBlock( blockType, blockContent, existingNotes = [] ) {
const result = await apiFetch( {
path: '/wp-abilities/v1/abilities/ai/editorial-notes/run',
method: 'POST',
data: {
input: {
block_type: blockType,
block_content: blockContent,
review_types: [ 'accessibility', 'readability', 'grammar', 'seo' ],
existing_notes: existingNotes,
context: String( postId ), // numeric post ID as string
},
},
} );
return result.suggestions; // Array of { review_type, text }
}| Code | Meaning |
|---|---|
block_content_required |
block_content was empty |
post_not_found |
The post ID passed does not exist |
insufficient_capabilities |
User lacks edit_posts (or edit_post for the specific post) |
-
Enable the experiment:
- Go to
Settings → AI - Enable the global toggle
- Enable AI Editorial Notes
- Ensure valid AI credentials are configured
- Go to
-
Run a review:
- Create or open a post with a mix of block types (headings, paragraphs, an image without alt text, a list)
- Open the post sidebar (click the Settings button in the toolbar)
- Click Generate Editorial Notes in the post info panel
- Watch the progress counter advance (
Reviewing blocks… 2 of 8) - After completion, open the Notes panel (via the block toolbar or the comments icon)
- Verify Notes appear on relevant blocks, formatted as
[REVIEW_TYPE] Suggestion text. - Verify Notes show "WordPress AI" as the author rather than your account name
-
Re-run accumulation:
- Click Generate Editorial Notes a second time
- Verify existing Note threads gain replies rather than new top-level Notes
- Verify prior suggestions are not repeated
-
Resolved Notes:
- Mark a Note as resolved in the Notes panel
- Run the review again
- Verify the resolved block is skipped entirely
-
Note deletion cleanup:
- Delete a Note from the Notes panel
- Save the post
- Verify the deleted block no longer has a
noteIdin its block metadata (inspect via the Code Editor)
-
Edge cases:
- Post with only very short blocks → button completes instantly with "No new suggestions found."
- All blocks already have Notes → second run skips repeats
- Disable experiment → button disappears from sidebar
- Valid AI credentials configured in
Settings → Connectors - User must have
edit_postscapability (oredit_postfor the specific post when a post ID is provided) - The block editor must be active (classic editor is not supported)
- Each block generates one API call; blocks are processed in parallel batches of 4
- The review is capped at 25 blocks per run to control cost
- Blocks with fewer than 20 characters of text are skipped
- Notes are stored as WordPress comments with
comment_type = 'note'andcomment_author = 'WordPress AI' - Block association is stored in
block.attributes.metadata.noteId - Block metadata is saved as part of the post content when the editor saves
- Note threads accumulate across review runs by design
- Deleting or trashing a root Note automatically clears
metadata.noteIdfrom its associated block
- Image block review is limited to alt text presence; it does not analyze the image itself
- Block metadata (
noteId) is only persisted after the post is saved - The 25-block cap means very long posts will have only the first 25 reviewable blocks analyzed per run
- Resolved blocks (approved Notes) are skipped in full; they will not receive new suggestions until the Note is un-resolved or deleted