Images

Generating Images Into the Media Library

⏱ 16 min

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.

What you'll learn in this lesson
The image generation builder chain
with_text(), as_output_file_type(), as_output_media_orientation(), and generate_image_result().
Reading the result object
GenerativeAiResult::toImageFile() and the File object it returns.
Saving into the media library
wp_upload_bits(), wp_insert_attachment(), and wp_generate_attachment_metadata() together.
Setting a baseline alt text
Storing something in _wp_attachment_image_alt immediately, refined properly in the next lesson.
Prerequisites

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.

Alt text here is a placeholder, not the real feature

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.

Not every provider supports image generation

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

Vision: Analyzing Images for Alt Text and Tagging →