Image generation is the one part of this course with no asterisk attached. It’s real,
it’s documented on developer.wordpress.org, and it produces an actual file you can drop
straight into the media library the same way an editor uploading a photo would. This
lesson builds that full path: a text prompt goes in, a WP_Post of type attachment
comes out, ready to use in a post, a featured image field, or anywhere else the media
library already works.
Course 5’s setup lessons: a configured provider plugin and a working
wp_ai_client_prompt() call. This lesson assumes the provider you’ve configured
supports image generation, not every provider does, check its capabilities before
relying on this in production.
Step 1: The image generation builder chain
Image generation uses the same wp_ai_client_prompt() entry point as text generation,
just a different chain of builder methods and a different terminal call. This chain
comes straight from WordPress’s own developer.wordpress.org tutorial on building an
image generation plugin with the AI Client:
// File: my-plugin/class-image-generator.php
use WordPress\AiClient\Files\Enums\FileTypeEnum;
use WordPress\AiClient\Files\Enums\MediaOrientationEnum;
$result = wp_ai_client_prompt()
->with_text( 'A watercolor illustration of a red panda reading a book, soft lighting' )
->as_output_file_type( FileTypeEnum::inline() )
->as_output_media_orientation( MediaOrientationEnum::from( 'landscape' ) )
->generate_image_result();
with_text() sets the prompt, the same method used for text generation. as_output_file_type( FileTypeEnum::inline() ) tells the provider to hand back the image as
inline base64 data rather than a URL you’d have to fetch separately. generate_image_result() is the terminal call, and note the name: it’s generate_image_result(), not
generate_text(), since an image result carries more than plain text does, it carries
an actual file.
Step 2: Choosing an orientation
as_output_media_orientation() is optional but worth setting deliberately rather than
leaving to the provider’s default. It accepts a MediaOrientationEnum built from one of
three string values:
// File: my-plugin/class-image-generator.php
MediaOrientationEnum::from( 'square' ); // 1:1, good for avatars and thumbnails
MediaOrientationEnum::from( 'landscape' ); // wide, good for featured images
MediaOrientationEnum::from( 'portrait' ); // tall, good for social/story formats
Pick the orientation based on where the image is going, a featured image slot and an Instagram-style story crop are different jobs, and asking the model for the right shape up front beats cropping a square image awkwardly after the fact.
Step 3: Getting the file out of the result
generate_image_result() returns a GenerativeAiResult, the same result type used
across the SDK, not a raw file. Pull the generated image out of it with toImageFile(),
which returns a File object:
// File: my-plugin/class-image-generator.php
$file = $result->toImageFile();
$base64_data = $file->getBase64Data();
$mime_type = $file->getMimeType();
$decoded = base64_decode( $base64_data );
toImageFile() throws if the first candidate in the result doesn’t actually contain
image content, worth knowing if you’re calling this from code that also handles text
results and want to fail loudly rather than silently, rather than quietly saving
nothing.
Step 4: Saving the file as a media library attachment
From here it’s standard WordPress media handling, nothing AI-specific about it.
wp_upload_bits() writes the decoded bytes to the uploads directory, wp_insert_attachment() registers it as a post, and wp_generate_attachment_metadata() builds
the resized versions WordPress expects an attachment to have:
// File: my-plugin/class-image-generator.php
function my_plugin_save_generated_image( string $decoded, string $mime_type, string $prompt ): int {
$extension = str_replace( 'image/', '', $mime_type );
$file_name = 'ai-generated-' . time() . '.' . $extension;
$upload = wp_upload_bits( $file_name, null, $decoded );
if ( ! empty( $upload['error'] ) ) {
return 0;
}
$attachment_id = wp_insert_attachment(
array(
'post_mime_type' => $mime_type,
'post_title' => sanitize_text_field( $prompt ),
'post_status' => 'inherit',
),
$upload['file']
);
if ( is_wp_error( $attachment_id ) ) {
return 0;
}
require_once ABSPATH . 'wp-admin/includes/image.php';
$metadata = wp_generate_attachment_metadata( $attachment_id, $upload['file'] );
wp_update_attachment_metadata( $attachment_id, $metadata );
update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $prompt ) );
return $attachment_id;
}
The require_once ABSPATH . 'wp-admin/includes/image.php' line matters, wp_generate_attachment_metadata() lives in an admin-only file that isn’t loaded on the front
end or during a REST request by default, calling it without that include fatal errors
outside wp-admin.
Setting _wp_attachment_image_alt to the prompt text itself gives every generated
image some alt text immediately, better than none, but the prompt you wrote to
generate an image isn’t necessarily a good description of what actually came back. The
next lesson covers using AI to look at the finished image and write real alt text for
it.
Step 5: Wiring it together end to end
// File: my-plugin/class-image-generator.php
function my_plugin_generate_image_to_library( string $prompt, string $orientation = 'landscape' ): int {
$result = wp_ai_client_prompt()
->with_text( $prompt )
->as_output_file_type( FileTypeEnum::inline() )
->as_output_media_orientation( MediaOrientationEnum::from( $orientation ) )
->generate_image_result();
$file = $result->toImageFile();
return my_plugin_save_generated_image(
base64_decode( $file->getBase64Data() ),
$file->getMimeType(),
$prompt
);
}
Test it: generate a real image
wp-env run cli wp eval '
$id = my_plugin_generate_image_to_library( "A minimalist icon of a coffee cup, flat design, blue background" );
echo "Attachment ID: $id\n";
echo "URL: " . wp_get_attachment_url( $id ) . "\n";
'
Open the returned URL in a browser. You should see a real generated image, and it should also show up in Media > Library in wp-admin like any other upload.
Text generation support is close to universal across providers, image generation isn’t.
Call isSupportedForImageGeneration() on your builder (or check the provider’s
documented capabilities) before shipping this in a plugin someone else will configure
with a provider you don’t control, calling generate_image_result() against a
provider that doesn’t support it fails at the API level, not with a friendly
in-WordPress error.
Recap
The confirmed, shipped chain for image generation is wp_ai_client_prompt()->with_text()->as_output_file_type( FileTypeEnum::inline() )->as_output_media_orientation( MediaOrientationEnum::from( ... ) )->generate_image_result(). The result is a
GenerativeAiResult, call toImageFile() on it to get a File object with
getBase64Data() and getMimeType(), decode that and hand it to the same
wp_upload_bits() / wp_insert_attachment() / wp_generate_attachment_metadata()
sequence any media upload uses. Everything past generate_image_result() is plain,
long-standing WordPress media API, nothing special about it being AI-generated.
Resources & further reading
- How to build an image generation plugin with the WordPress AI Client, developer.wordpress.org
- PHP AI Client SDK repository, GitHub
- wp_insert_attachment() function reference, developer.wordpress.org