This project builds an ability that, given a post ID, generates a meta description for the post and alt text for every image attached to it, in one call. It’s built to run across many posts at once, since meta descriptions and alt text are exactly the kind of small, repetitive task that piles up across hundreds of older posts.
Course 5 for wp_ai_client_prompt(). Test posts should have at least one attached
image with post_parent set to the post, so there’s something real for the alt-text
half to work on.
Step 1: Register a bulk-capable ability
input_schema accepts an array of post IDs, not a single one, so one call can cover
many posts.
// File: seo-metadata/seo-metadata.php
add_action( 'wp_abilities_api_init', function () {
wp_register_ability(
'seo-metadata/generate',
array(
'label' => __( 'Generate meta descriptions and alt text', 'seo-metadata' ),
'description' => __( 'Generates a meta description and image alt text for one or more posts. Alt text describes what each image conveys, for accessibility, not for keyword stuffing.', 'seo-metadata' ),
'category' => 'content',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'post_ids' => array( 'type' => 'array', 'items' => array( 'type' => 'integer' ) ),
),
'required' => array( 'post_ids' ),
),
'output_schema' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'post_id' => array( 'type' => 'integer' ),
'meta_description' => array( 'type' => 'string' ),
'images_updated' => array( 'type' => 'integer' ),
),
),
),
'permission_callback' => function ( $input ) {
foreach ( $input['post_ids'] as $id ) {
if ( ! current_user_can( 'edit_post', $id ) ) {
return false;
}
}
return true;
},
'execute_callback' => 'seo_metadata_generate_for_posts',
'meta' => array(
'annotations' => array( 'destructive' => false, 'idempotent' => true ),
'mcp' => array( 'public' => true ),
),
)
);
} );
permission_callback checks every post ID in the batch individually, not just
whether the caller can edit posts in general, so a bulk call can’t sneak through
edits on content the caller doesn’t actually have rights to.
Step 2: Generate for each post, using the AI Client SDK
// File: seo-metadata/seo-metadata.php
function seo_metadata_generate_for_posts( $input ) {
$results = array();
foreach ( $input['post_ids'] as $post_id ) {
$post = get_post( $post_id );
if ( ! $post ) {
continue;
}
$body = wp_strip_all_tags( $post->post_content );
$meta_prompt = "Write an SEO meta description for this post, under 155 characters, accurately summarizing the content, no clickbait:\n\nTitle: {$post->post_title}\nContent: {$body}";
$meta_description = wp_ai_client_prompt( $meta_prompt )->generate_text();
if ( ! empty( $meta_description ) ) {
update_post_meta( $post_id, '_seo_meta_description', sanitize_text_field( trim( $meta_description ) ) );
}
$images_updated = seo_metadata_generate_alt_text_for_images( $post_id, $post->post_title );
$results[] = array(
'post_id' => $post_id,
'meta_description' => sanitize_text_field( trim( $meta_description ) ),
'images_updated' => $images_updated,
);
}
return $results;
}
function seo_metadata_generate_alt_text_for_images( $post_id, $post_title ) {
$images = get_posts( array(
'post_type' => 'attachment',
'post_parent' => $post_id,
'post_mime_type' => 'image',
'posts_per_page' => -1,
) );
$updated = 0;
foreach ( $images as $image ) {
$existing_alt = get_post_meta( $image->ID, '_wp_attachment_image_alt', true );
if ( ! empty( $existing_alt ) ) {
continue; // Never overwrite alt text a human already wrote.
}
$image_url = wp_get_attachment_url( $image->ID );
$prompt = "This image appears in a post titled \"{$post_title}\". Based on its filename and context, write concise, descriptive alt text (under 125 characters) that conveys what the image shows, for a screen reader user, not for search engines. Filename: {$image->post_title}";
$alt_text = wp_ai_client_prompt( $prompt )->generate_text();
if ( ! empty( $alt_text ) ) {
update_post_meta( $image->ID, '_wp_attachment_image_alt', sanitize_text_field( trim( $alt_text ) ) );
$updated++;
}
}
return $updated;
}
Step 3: Why alt text is framed as accessibility first, not SEO first
The prompt explicitly says “for a screen reader user, not for search engines.” This
matters: alt text that’s optimized purely for keyword density tends to be stuffed and
unnatural, exactly the opposite of what a screen reader user actually needs, a plain,
accurate description of what the image shows. Good alt text tends to help SEO as a
side effect of being genuinely useful, but writing it backward, for search engines
first, produces worse accessibility and often worse SEO outcomes too. Also note the
code never overwrites alt text a human already wrote, get_post_meta() is checked
first and the loop skips any image that already has one.
Test it
Run the ability against one or more real post IDs with attached images:
wp-env run cli wp eval '
$result = wp_get_ability( "seo-metadata/generate" )->execute( array(
"post_ids" => array( 42, 43 ),
) );
print_r( $result );
'
wp-env run cli wp post meta get 42 _seo_meta_description
Confirm the meta description is under 155 characters and accurately reflects the post, then check one of the post’s images in wp-admin’s media library to confirm alt text was filled in.
Passing hundreds of post IDs in one call means hundreds of sequential AI provider calls (one per meta description, one per image), which can run long enough to hit a PHP execution time limit or an API rate limit. For a real bulk run across an entire site, process in smaller batches (a WP-CLI command looping in chunks of 20 to 50) rather than one enormous ability call.
Recap
One ability call generates both a meta description and alt text for every image on a
post, or a batch of posts, using wp_ai_client_prompt() for each piece of text and
writing results to post meta and the attachment’s own alt-text field. Existing alt
text is never overwritten, and the alt-text prompt is deliberately framed around what
a screen reader user needs, not around keyword density.
Resources & further reading
- update_post_meta() function reference, developer.wordpress.org
- PHP AI Client SDK repository, GitHub
- Alt text (WordPress accessibility handbook), make.wordpress.org