WordPress Image Automation: Generate Featured Images with API
Every WordPress post needs a featured image. If you publish regularly, that's a lot of images. And if you're running multiple sites? The design work compounds fast.
Most people solve this by buying stock photos or spending time in Canva. Neither scales well. Stock photos look generic, and manual design eats hours every week.
Here's the alternative: generate featured images automatically from your post data. The title becomes the headline on a branded template. Publish a post, get an image. No extra work.
I've been running this setup on three WordPress sites for over a year. Here's exactly how to build it.
Why automate wordpress featured imagesWhy Automate WordPress Featured Images?
The numbers tell the story:
| Scenario | Manual Time | Automated Time |
|---|---|---|
| 10 posts/month | 2-3 hours | 0 hours |
| 50 posts/month | 10-15 hours | 0 hours |
| Multi-site (3 sites) | 6-9 hours | 0 hours |
That's time you can spend on content, promotion, or literally anything else.
What automation gets you:
- Consistent branding across all posts
- Instant image generation on publish
- No dependency on designers or stock sites
- Easy updates when you rebrand
The only upfront work is creating your template. Everything after that runs on autopilot.
The architectureThe Architecture
WordPress doesn't have built-in image generation, so we connect external services. Three approaches work:
- Webhook + Cloud Function - WordPress triggers a function that generates images
- Cron Job - A script runs periodically to process posts without images
- Plugin + API - A lightweight plugin calls the image API on post save
I'll cover all three. Pick based on your technical comfort level.
Method 1 webhook with serverless function recommendedMethod 1: Webhook with Serverless Function (Recommended)
This is the cleanest approach. WordPress sends a webhook when posts publish, a cloud function generates the image, and updates the post.
Step 1 create the cloud functionStep 1: Create the Cloud Function
Using Vercel, Netlify, or AWS Lambda:
// api/generate-wp-image.js
export default async function handler(req, res) {
const { post_id, title, site_url, auth_token } = req.body
// Generate image from template
const imageResponse = await fetch(
`https://render.imejis.io/v1/${process.env.IMEJIS_TEMPLATE_ID}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.IMEJIS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
headline: title,
}),
}
)
// API returns image directly
const imageBuffer = await imageResponse.arrayBuffer()
// Upload to WordPress media library
const mediaResponse = await fetch(`${site_url}/wp-json/wp/v2/media`, {
method: "POST",
headers: {
Authorization: `Bearer ${auth_token}`,
"Content-Type": "image/png",
"Content-Disposition": `attachment; filename="featured-${post_id}.png"`,
},
body: Buffer.from(imageBuffer),
})
const media = await mediaResponse.json()
// Set as featured image
await fetch(`${site_url}/wp-json/wp/v2/posts/${post_id}`, {
method: "POST",
headers: {
Authorization: `Bearer ${auth_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
featured_media: media.id,
}),
})
res.status(200).json({ success: true, mediaId: media.id })
}Step 2 add webhook to wordpressStep 2: Add Webhook to WordPress
Install a webhook plugin or add this to your theme's functions.php:
// functions.php
add_action('publish_post', 'trigger_image_generation', 10, 2);
function trigger_image_generation($post_id, $post) {
// Skip if already has featured image
if (has_post_thumbnail($post_id)) {
return;
}
// Skip revisions and autosaves
if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
return;
}
$webhook_url = 'https://your-function.vercel.app/api/generate-wp-image';
wp_remote_post($webhook_url, array(
'body' => json_encode(array(
'post_id' => $post_id,
'title' => $post->post_title,
'site_url' => get_site_url(),
'auth_token' => get_option('image_gen_token') // Store securely
)),
'headers' => array(
'Content-Type' => 'application/json'
),
'timeout' => 30
));
}Step 3 set up authenticationStep 3: Set Up Authentication
For the WordPress REST API, you need authentication. Options:
- Application Passwords (WordPress 5.6+) - Built-in, easiest
- JWT Authentication - More secure for production
- OAuth - For public-facing apps
For most setups, Application Passwords work fine:
- Go to Users > Your Profile
- Scroll to "Application Passwords"
- Generate a new password
- Store it as an environment variable
Method 2 cron job approachMethod 2: Cron Job Approach
If webhooks feel complex, run a script periodically to process posts missing featured images.
The scriptThe Script
// scripts/process-wp-images.js
const fetch = require("node-fetch")
const WP_SITE = process.env.WP_SITE_URL
const WP_USER = process.env.WP_USERNAME
const WP_PASS = process.env.WP_APP_PASSWORD
const IMEJIS_KEY = process.env.IMEJIS_API_KEY
const TEMPLATE_ID = process.env.IMEJIS_TEMPLATE_ID
async function getPostsWithoutImages() {
const auth = Buffer.from(`${WP_USER}:${WP_PASS}`).toString("base64")
const response = await fetch(
`${WP_SITE}/wp-json/wp/v2/posts?per_page=50&_fields=id,title,featured_media`,
{
headers: { Authorization: `Basic ${auth}` },
}
)
const posts = await response.json()
return posts.filter((post) => post.featured_media === 0)
}
async function generateAndUploadImage(post) {
const auth = Buffer.from(`${WP_USER}:${WP_PASS}`).toString("base64")
// Generate image
const imageResponse = await fetch(
`https://render.imejis.io/v1/${TEMPLATE_ID}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${IMEJIS_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
headline: post.title.rendered,
}),
}
)
const imageBuffer = await imageResponse.arrayBuffer()
// Upload to WordPress
const mediaResponse = await fetch(`${WP_SITE}/wp-json/wp/v2/media`, {
method: "POST",
headers: {
Authorization: `Basic ${auth}`,
"Content-Type": "image/png",
"Content-Disposition": `attachment; filename="featured-${post.id}.png"`,
},
body: Buffer.from(imageBuffer),
})
const media = await mediaResponse.json()
// Set featured image
await fetch(`${WP_SITE}/wp-json/wp/v2/posts/${post.id}`, {
method: "POST",
headers: {
Authorization: `Basic ${auth}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ featured_media: media.id }),
})
console.log(`Generated image for: ${post.title.rendered}`)
}
async function main() {
const posts = await getPostsWithoutImages()
console.log(`Found ${posts.length} posts without featured images`)
for (const post of posts) {
await generateAndUploadImage(post)
// Rate limit
await new Promise((r) => setTimeout(r, 2000))
}
}
main()Running the scriptRunning the Script
Set up a cron job to run this periodically:
# Run every hour
0 * * * * cd /path/to/scripts && node process-wp-images.jsOr use a service like GitHub Actions:
# .github/workflows/wp-images.yml
name: Generate WordPress Images
on:
schedule:
- cron: "0 * * * *" # Every hour
workflow_dispatch: # Manual trigger
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm install node-fetch
- run: node scripts/process-wp-images.js
env:
WP_SITE_URL: ${{ secrets.WP_SITE_URL }}
WP_USERNAME: ${{ secrets.WP_USERNAME }}
WP_APP_PASSWORD: ${{ secrets.WP_APP_PASSWORD }}
IMEJIS_API_KEY: ${{ secrets.IMEJIS_API_KEY }}
IMEJIS_TEMPLATE_ID: ${{ secrets.IMEJIS_TEMPLATE_ID }}Method 3 lightweight pluginMethod 3: Lightweight Plugin
For those who prefer keeping everything in WordPress:
<?php
/**
* Plugin Name: Auto Featured Images
* Description: Generates featured images automatically using Imejis.io
* Version: 1.0
*/
// Settings page
add_action('admin_menu', 'afi_add_settings_page');
function afi_add_settings_page() {
add_options_page(
'Auto Featured Images',
'Auto Featured Images',
'manage_options',
'auto-featured-images',
'afi_settings_page'
);
}
function afi_settings_page() {
?>
<div class="wrap">
<h1>Auto Featured Images Settings</h1>
<form method="post" action="options.php">
<?php settings_fields('afi_settings'); ?>
<table class="form-table">
<tr>
<th>Imejis.io API Key</th>
<td><input type="text" name="afi_api_key" value="<?php echo esc_attr(get_option('afi_api_key')); ?>" class="regular-text"></td>
</tr>
<tr>
<th>Template ID</th>
<td><input type="text" name="afi_template_id" value="<?php echo esc_attr(get_option('afi_template_id')); ?>" class="regular-text"></td>
</tr>
</table>
<?php submit_button(); ?>
</form>
</div>
<?php
}
add_action('admin_init', 'afi_register_settings');
function afi_register_settings() {
register_setting('afi_settings', 'afi_api_key');
register_setting('afi_settings', 'afi_template_id');
}
// Generate image on post publish
add_action('publish_post', 'afi_generate_image', 10, 2);
function afi_generate_image($post_id, $post) {
if (has_post_thumbnail($post_id)) return;
if (wp_is_post_revision($post_id)) return;
$api_key = get_option('afi_api_key');
$template_id = get_option('afi_template_id');
if (!$api_key || !$template_id) return;
// Generate image
$response = wp_remote_post(
"https://render.imejis.io/v1/{$template_id}",
array(
'headers' => array(
'Authorization' => "Bearer {$api_key}",
'Content-Type' => 'application/json'
),
'body' => json_encode(array(
'headline' => $post->post_title
)),
'timeout' => 30
)
);
if (is_wp_error($response)) return;
$image_data = wp_remote_retrieve_body($response);
// Upload to media library
$upload = wp_upload_bits(
"featured-{$post_id}.png",
null,
$image_data
);
if ($upload['error']) return;
$attachment = array(
'post_mime_type' => 'image/png',
'post_title' => $post->post_title,
'post_content' => '',
'post_status' => 'inherit'
);
$attach_id = wp_insert_attachment($attachment, $upload['file'], $post_id);
require_once(ABSPATH . 'wp-admin/includes/image.php');
$attach_data = wp_generate_attachment_metadata($attach_id, $upload['file']);
wp_update_attachment_metadata($attach_id, $attach_data);
set_post_thumbnail($post_id, $attach_id);
}Save as auto-featured-images.php in wp-content/plugins/. Activate and configure in Settings > Auto Featured Images.
Template design tipsTemplate Design Tips
Your featured image template should work with any title length.
Handling long titlesHandling Long Titles
Option 1: Auto-shrink text Most image APIs (including Imejis.io) support auto-sizing text to fit the container.
Option 2: Truncate in code
const headline = title.length > 60 ? title.substring(0, 57) + "..." : titleOption 3: Design for the worst case Build your template assuming 80-character titles. Shorter titles just have more whitespace.
Recommended dimensionsRecommended Dimensions
| Use Case | Dimensions |
|---|---|
| Blog featured | 1200 x 630px |
| Social sharing | 1200 x 630px |
| WordPress thumbnail | 150 x 150px (auto-generated) |
Design at 1200 x 630px. WordPress generates smaller sizes automatically.
Woocommerce product imagesWooCommerce Product Images
The same approach works for WooCommerce products:
add_action('publish_product', 'afi_generate_product_image', 10, 2);
function afi_generate_product_image($post_id, $post) {
// Same logic, different template
// Include price, product image, etc.
}Pass product data to a product-specific template:
{
"product_name": "Wireless Headphones",
"price": "$149.99",
"product_image": "https://your-site.com/wp-content/uploads/headphones.jpg"
}TroubleshootingTroubleshooting
Images not generatingImages Not Generating
Check:
- Is the API key correct?
- Is the template ID right?
- Are WordPress REST API endpoints accessible?
Debug: Add logging to your functions:
error_log('Image generation triggered for post: ' . $post_id);Images not uploading to wordpressImages Not Uploading to WordPress
Check:
- Does the user have upload permissions?
- Is the uploads directory writable?
- Is the file size within limits?
Fix: Check wp-content/uploads permissions (should be 755).
Webhook timeoutWebhook Timeout
Check:
- Is the cloud function responding within WordPress's timeout?
- Is the image generation fast enough?
Fix: Increase timeout or use async processing with a queue.
Cost analysisCost Analysis
| Component | Monthly Cost |
|---|---|
| Vercel (free tier) | $0 |
| Imejis.io Basic | $14.99 |
| Total | $14.99 |
For 1,000 posts per month, that's about 1.5 cents per featured image. Compare to stock photo subscriptions ($20-100/month) or designer time.
Check Imejis.io pricing for higher volume needs.
Getting startedGetting Started
Here's your path:
- Create your template in Imejis.io with headline placeholder
- Choose your method: webhook, cron, or plugin
- Set up authentication for WordPress REST API
- Test with one post before rolling out
- Process existing posts with the cron script
Start simple. One template, one post type. Expand once the basics work.
Your WordPress content deserves better than generic stock photos. Automated branded images make every post look intentional, without the manual work.
Get started with Imejis.io free
FaqFAQ
Will this work with any wordpress themeWill this work with any WordPress theme?
Yes. Featured images are a WordPress core feature. The automation sets the featured image the same way you would manually. Any theme that supports featured images will display them.
Can i generate different images for different categoriesCan I generate different images for different categories?
Yes. Check the post's category in your function and use different template IDs:
$category = get_the_category($post_id);
$template_id = $category[0]->slug === 'news' ? 'news-template' : 'default-template';What happens if the api is down when i publishWhat happens if the API is down when I publish?
The post publishes without a featured image. The cron approach handles this better since it retries periodically. For the webhook method, add error handling and a fallback.
Does this work with gutenberg and the block editorDoes this work with Gutenberg and the block editor?
Yes. The publish_post hook fires regardless of which editor you use. Classic editor, Gutenberg, or even the REST API directly all trigger the same hook.
Can i regenerate images for existing postsCan I regenerate images for existing posts?
Yes. Use the cron script to process posts without featured images. Or modify it to regenerate all images (useful after a rebrand).

