This is the first project lesson, and it pairs a custom ability with the PHP AI Client
SDK from Course 1: given a product ID, gather its real, existing attributes, then use
wp_ai_client_prompt() to turn that into a written description. The important design
constraint here is that the model only ever writes from data your own PHP already
pulled from the product, it never invents specifics.
WordPress 7.0+ with at least one AI provider plugin configured (Course 1, Lesson 7). WooCommerce with at least one product that has attributes assigned. No specific WooCommerce version requirement, this lesson doesn’t use the 10.9 abilities directly.
Step 1: Gathering the grounding data
// File: my-plugin.php
function my_plugin_get_product_context( $product_id ) {
$product = wc_get_product( $product_id );
if ( ! $product ) {
return null;
}
$attributes = array();
foreach ( $product->get_attributes() as $attribute ) {
if ( is_a( $attribute, 'WC_Product_Attribute' ) ) {
$attributes[ $attribute->get_name() ] = implode( ', ', $attribute->get_options() );
}
}
return array(
'name' => $product->get_name(),
'categories' => wp_get_post_terms( $product_id, 'product_cat', array( 'fields' => 'names' ) ),
'attributes' => $attributes,
'existing_desc' => $product->get_short_description(),
);
}
This function is deliberately separate from the ability’s execute_callback, it’s
plain grounding-data logic you can unit test or reuse elsewhere.
Step 2: Registering the ability
// File: my-plugin.php
add_action( 'wp_abilities_api_init', function () {
wp_register_ability(
'my-plugin/generate-product-description',
array(
'label' => __( 'Generate product description', 'my-plugin' ),
'description' => __( 'Generates a draft product description from a product\'s existing name, category, and attributes. Does not publish it, returns a draft for review.', 'my-plugin' ),
'category' => 'store-inventory',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'product_id' => array( 'type' => 'integer' ),
),
'required' => array( 'product_id' ),
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'draft_description' => array( 'type' => 'string' ),
),
),
'permission_callback' => function () {
return current_user_can( 'edit_products' );
},
'execute_callback' => function ( $input ) {
$context = my_plugin_get_product_context( $input['product_id'] );
if ( null === $context ) {
return array( 'draft_description' => '' );
}
$prompt = sprintf(
"Write a two-sentence WooCommerce product description.\n" .
"Use ONLY the facts below, do not invent features, materials, or specs not listed.\n" .
"Product name: %s\nCategories: %s\nAttributes: %s\n" .
"Existing description (may be empty): %s",
$context['name'],
implode( ', ', $context['categories'] ),
wp_json_encode( $context['attributes'] ),
$context['existing_desc']
);
$draft = wp_ai_client_prompt( $prompt )->generate_text();
return array( 'draft_description' => trim( $draft ) );
},
'meta' => array(
'annotations' => array( 'readonly' => true ),
'mcp' => array( 'public' => false ),
),
)
);
} );
Even though this ability doesn’t modify the database (it returns a draft rather than
saving anything), it’s marked readonly in annotations and kept off
meta.mcp.public for now, deliberately, until you’ve decided whether external agents
should be able to trigger AI generation calls (and incur provider costs) on your site
directly.
Step 3: Writing the reviewed draft back to the product
Once a human (or your own admin UI) approves a draft, saving it is a separate, explicit step, not something the generation ability does itself:
// File: my-plugin.php
function my_plugin_apply_product_description( $product_id, $description ) {
$product = wc_get_product( $product_id );
if ( ! $product ) {
return false;
}
$product->set_short_description( wp_kses_post( $description ) );
$product->save();
return true;
}
Keeping generation and saving as two separate functions (and two separate, independently permission-checked steps if you expose both as abilities) means a generated draft can be reviewed, edited, or discarded before anything touches the live product.
Test it
wp-env run cli wp eval '
$ability = wp_get_ability( "my-plugin/generate-product-description" );
$result = $ability->execute( array( "product_id" => 42 ) );
echo $result["draft_description"];
'
Read the output against the product’s actual attributes. If it mentions anything not present in what you fed it, the prompt needs tightening, not the model.
Even with an explicit “use only the facts below” instruction, models sometimes pad thin attribute data with vague, generic phrases (“perfect for any occasion,” “premium quality”). This isn’t hallucinated fact, but it’s not useful copy either. If your product data is sparse, expect to either enrich the input (categories, tags, existing reviews) or treat the output as a first draft that still needs a human pass, not something to auto-publish.
Recap
This ability pairs wc_get_product()-sourced grounding data with
wp_ai_client_prompt() to generate description drafts, keeping generation and saving
as two separate steps so nothing publishes without review. The pattern of gathering
real data first, then prompting only from that data, is one you’ll reuse in every
remaining project lesson in this course.
Resources & further reading
- WC_Product::get_attributes() reference, woocommerce.github.io
- PHP AI Client SDK repository, GitHub
- Introducing the AI Client in WordPress 7.0, make.wordpress.org