How to Automate Image Creation for WordPress
Build a WordPress plugin that generates, reviews, and uploads AI images with the AI Client, then deliver responsive Media Library assets reliably.

Direct answer: automate image creation in WordPress with a small plugin that calls the WordPress AI Client, previews the returned image, and only then uploads it to the Media Library. This keeps provider credentials on the server, works with whichever configured model supports image generation, and lets WordPress create responsive sizes after upload.
The workflow below targets WordPress 7.0 or later. It assumes PHP 7.4+ for the plugin example, Node.js 20+ for building the JavaScript bundle, an AI Client connection configured by the site owner, and a provider/model that advertises image-generation capability. The provider can be changed in Settings → Connectors; your code should not hard-code OpenAI, Google AI, or any other vendor.
1. Decide what “automated” means
There are three separate operations:

- Generation: an external model turns a text prompt into image data.
- Review and upload: an editor previews the result, then your plugin creates a Media Library attachment.
- Delivery: WordPress serves the attachment and generates
srcset/sizesvariants for responsive layouts.
Keeping these stages separate avoids publishing an unreviewed image and makes failures easier to retry. WordPress responsive images improve delivery, but they do not guarantee universal compression, art direction, or perfect performance for every theme.
2. Check prerequisites and provider capability
- WordPress 7.0 or newer.
- PHP 7.4 or newer for the sample plugin.
- Node.js 20 or newer if you compile the admin interface.
- The WordPress AI Client plugin/library is installed and active.
- A connection in Settings → Connectors whose selected model supports image generation.
- An administrator or editor with the
upload_filescapability for the upload action.
Do not show a “Generate” button merely because the AI Client function exists. Check both that the function is available and that the configured client reports image-generation support. If no provider is configured, show an actionable settings message instead of sending a request that cannot succeed.
3. Create the plugin shell
Create wp-content/plugins/ai-media-generator/ai-media-generator.php:
<?php
/**
* Plugin Name: AI Media Generator
* Description: Generate an image with the WordPress AI Client, preview it, and upload it to Media.
* Version: 0.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action( 'rest_api_init', function () {
register_rest_route( 'ai-media/v1', '/generate', [
'methods' => 'POST',
'callback' => 'aimg_generate',
'permission_callback' => function () {
return current_user_can( 'upload_files' );
},
'args' => [
'prompt' => [
'required' => true,
'sanitize_callback' => 'sanitize_textarea_field',
],
],
] );
register_rest_route( 'ai-media/v1', '/upload', [
'methods' => 'POST',
'callback' => 'aimg_upload',
'permission_callback' => function () {
return current_user_can( 'upload_files' );
},
] );
} );
function aimg_generate( WP_REST_Request $request ) {
if ( ! function_exists( 'wp_ai_client_prompt' ) ) {
return new WP_Error( 'ai_client_missing', 'The WordPress AI Client is not available.', [ 'status' => 503 ] );
}
$prompt = trim( (string) $request['prompt'] );
if ( '' === $prompt ) {
return new WP_Error( 'empty_prompt', 'Enter an image prompt.', [ 'status' => 400 ] );
}
try {
$result = wp_ai_client_prompt( $prompt )
->with_image_generation()
->generate();
// Return only data needed by the preview UI. Adapt this extraction to the
// AI Client version installed on the site.
$image_data = $result->get_inline_image_data();
if ( ! $image_data ) {
return new WP_Error( 'no_image', 'The provider returned no image data.', [ 'status' => 502 ] );
}
return [
'mime' => $image_data['mime_type'],
'data' => $image_data['data'],
];
} catch ( Throwable $e ) {
return new WP_Error( 'generation_failed', $e->getMessage(), [ 'status' => 502 ] );
}
}
function aimg_upload( WP_REST_Request $request ) {
$body = $request->get_json_params();
$data = isset( $body['data'] ) ? base64_decode( $body['data'], true ) : false;
$mime = isset( $body['mime'] ) ? sanitize_mime_type( $body['mime'] ) : 'image/png';
if ( false === $data || 0 === strlen( $data ) ) {
return new WP_Error( 'invalid_image', 'No valid image payload was supplied.', [ 'status' => 400 ] );
}
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
$extension = 'image/jpeg' === $mime ? 'jpg' : ( 'image/webp' === $mime ? 'webp' : 'png' );
$filename = wp_unique_filename( wp_upload_dir()['path'], 'ai-generated.' . $extension );
$upload = wp_upload_bits( $filename, null, $data );
if ( $upload['error'] ) {
return new WP_Error( 'upload_failed', $upload['error'], [ 'status' => 500 ] );
}
$attachment_id = wp_insert_attachment( [
'post_mime_type' => $mime,
'post_title' => 'AI generated image',
'post_status' => 'inherit',
], $upload['file'] );
if ( is_wp_error( $attachment_id ) ) {
return $attachment_id;
}
wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $upload['file'] ) );
return [
'id' => $attachment_id,
'url' => wp_get_attachment_url( $attachment_id ),
];
}
The exact AI Client response helpers can evolve, so keep the provider/model abstraction in the client and isolate response extraction in one function. Never place provider keys in browser JavaScript. The REST permission callback protects both routes; add nonce validation for requests made from an authenticated admin screen.
4. Add a review-first admin interface
Your interface needs a prompt field, a Generate button, a preview, and an Upload button that remains disabled until an image exists. A minimal browser call looks like this:
async function generate(prompt) {
const response = await wp.apiFetch({
path: '/ai-media/v1/generate',
method: 'POST',
data: { prompt }
});
preview.src = `data:${response.mime};base64,${response.data}`;
uploadButton.disabled = false;
window.generatedImage = response;
}
async function upload() {
const result = await wp.apiFetch({
path: '/ai-media/v1/upload',
method: 'POST',
data: window.generatedImage
});
mediaUrl.textContent = result.url;
}
In production, add a request identifier, disable duplicate clicks, show progress, and clear the preview after a successful upload. Store the prompt as attachment metadata if editors need to reproduce an image, but avoid putting sensitive prompts in public post content.
5. Upload through the standard Media REST API when appropriate
WordPress also exposes the authenticated media collection at /wp/v2/media. Use it when you already have a file and want the normal media permissions, fields, and query behavior. The custom route above is useful because it can validate the generated payload and attach prompt metadata before delegating to core functions. Do not confuse a plugin-specific route such as /ai-media/v1/upload with the core /wp/v2/media endpoint.
const form = new FormData();
form.append('file', blob, 'generated.png');
form.append('title', 'AI generated image');
const response = await fetch('/wp-json/wp/v2/media', {
method: 'POST',
headers: { 'X-WP-Nonce': window.wpApiSettings.nonce },
body: form
});
if (!response.ok) throw new Error(await response.text());
const attachment = await response.json();
Use authenticated cookies and a REST nonce in the browser, or application passwords/server authentication for a trusted backend. Do not expose long-lived credentials in page HTML.
6. Make generation reliable
Prompt and validation
- Set a maximum prompt length and reject empty input.
- Tell the model the intended aspect ratio, subject, lighting, and exclusions.
- Validate the returned MIME type and decode the payload strictly.
- Generate a unique filename and never overwrite an existing attachment.
Retries and idempotency
Providers can time out after generating an image. Give the request a bounded timeout, retry transient 5xx responses with exponential backoff, and include an idempotency key if your provider supports one. Keep generation and upload separate so a failed upload does not consume another generation. Log a request ID and status, but do not log provider secrets or full private prompts.
Queues and scheduled jobs
For batches, enqueue jobs with Action Scheduler or your existing queue. Return a job ID immediately, process generation in the worker, and let the editor review completed previews. Limit concurrency to the provider’s allowance and to your PHP worker memory. A cron task can clean abandoned temporary files after a retention period.
7. Responsive images after upload
Since WordPress 4.4, core can emit srcset and sizes and create smaller image sizes during upload. Themes and plugins may register additional sizes. Use the generated attachment ID in post content so WordPress can select an appropriate source for each viewport. Verify the actual sizes on your site; responsive delivery is not the same as automatic compression or art direction.

$html = wp_get_attachment_image(
$attachment_id,
'large',
false,
[ 'loading' => 'lazy', 'decoding' => 'async' ]
);
echo $html;
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| “AI Client is not available” | Plugin inactive or function not loaded. | Install/activate the AI Client, check load order, and gate the UI with function_exists(). |
| No image-generation option | Configured provider/model lacks that capability. | Open Settings → Connectors and select a model that reports image generation; do not assume a vendor. |
| 401 or 403 on upload | Missing REST nonce, expired login, or no upload_files capability. |
Refresh authentication, send X-WP-Nonce, and verify the user role. |
| 502 from generate | Provider timeout, quota, or malformed response. | Inspect server logs, apply bounded retries, check provider limits, and validate inline image data. |
| Image uploads but has no thumbnails | Metadata generation failed or unsupported MIME. | Confirm PHP image support, use a supported MIME, and rerun wp_generate_attachment_metadata(). |
| Large or slow admin requests | Base64 image is too large for PHP or proxy limits. | Raise limits carefully, use asynchronous jobs, or store temporary files instead of sending huge JSON bodies. |
| Duplicate images after refresh | Upload retried without an idempotency check. | Disable the button after submit and persist a client/job ID to deduplicate. |
9. Performance, reliability, and cost planning
- Latency: generation dominates; keep preview and upload separate and use background jobs for batches.
- Bandwidth: inline base64 increases payload size. For larger images, pass a temporary server-side file between generation and upload.
- Storage: every upload can create multiple registered sizes. Define retention rules for rejected previews and unused variants.
- Provider billing: the AI provider connection controls model charges and quotas. WordPress does not standardize those prices; expose usage and failures in your own logs.
- Security: authorize routes, sanitize prompts and filenames, validate MIME/content, and keep credentials server-side.
10. Advanced orchestration with Abilities and MCP
If an AI agent must discover and run image-generation actions, the WordPress Abilities API, AI Client, and MCP Adapter can expose registered abilities to that agent. This is an orchestration layer, not a requirement for the basic Media Library flow. Define a narrow ability such as generate_media_image, document its inputs and permissions, and still require an explicit review step before publishing.
Or skip the browser setup
If your automation also needs screenshots of the finished WordPress page, ScreenshotNeo gives you a single GET request instead of maintaining a browser worker. Cookie banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. It also provides an MCP server for AI agents, 1,000 screenshots per month free with no card, and paid plans starting at $5 for 3,000 shots.
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(await res.text());
require('fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));
See the ScreenshotNeo API documentation for the 63 capture options, including full-page and element shots, custom CSS/JavaScript, waits, blocking, headers, cookies, device presets, PDFs, caching, signed links, async jobs, bulk capture, and usage reporting. Responses include X-Page-Verdict and X-Billed headers so your pipeline can distinguish clean, billable captures from failed or free outcomes. Create a free ScreenshotNeo account to get 1,000 screenshots each month with no card.
FAQ
Does every WordPress site have image generation enabled?
No. The site owner must configure a provider/model through the AI Client, and that model must support image generation.
Should generation immediately create a Media attachment?
Usually no. Return a preview first, then upload after editorial approval so rejected results do not pollute the library.
Can I use the core media endpoint?
Yes, when you already have a file and authenticated REST access. A custom route is useful for validation, prompt metadata, and a tailored review workflow.
Will WordPress automatically optimize every generated image?
WordPress can create registered sizes and responsive srcset/sizes attributes, but compression and art direction depend on your installation and theme.
What is the safest place for provider credentials?
Server-side connector configuration or protected environment settings. Never embed keys in browser code or public post content.
Implementation checklist
- Confirm WordPress, PHP, Node, and AI Client versions.
- Configure an image-capable provider in Settings → Connectors.
- Gate the UI and routes on capability and
upload_files. - Generate, preview, approve, then upload.
- Validate MIME, decode data strictly, and generate unique filenames.
- Add nonce/authentication, bounded retries, logging, and cleanup.
- Render attachments with WordPress responsive image helpers.
- Use asynchronous jobs for batches and document provider costs for editors.


