Blog Image Generation
[To Build] ·
agent__blog-image-generator· Azure OpenAI GPT Image
Generates a hero image and optional inline section images for blog posts using Azure OpenAI GPT Image. Images are brand-matched using the tenant’s BrandAsset colours and style preferences, uploaded to DigitalOcean Spaces, and embedded into the blog post Markdown automatically.
Related: Blog Writer · Blog Image Generator Agent · Social Post Designer · Content Toolkit Overview
Overview
| Function | Generate brand-matched hero and inline images for blog posts |
| Type | Worker — Content |
| Status | To Build |
| Priority | P3 — Growth |
| Queue | agent__blog-image-generator |
| Concurrency | 2 |
| Timeout | 5 min |
| Est. cost / task | ~$0.08 per hero image (Azure GPT Image standard quality) |
| Credits | 0.5 cr hero image · 0.25 cr per inline image |
| Plan | Pro+ |
Why This Is Needed
Blog posts are published to WordPress as text-only. No featured image is set, and no inline images accompany sections. This creates two problems:
- Visual quality: WordPress posts without a featured image show a blank thumbnail in post lists and social sharing previews (Open Graph).
- Engagement: Long-form posts without visual breaks have higher bounce rates. Inline illustrations between sections improve readability.
The social-post-designer agent already generates branded poster images using Azure GPT Image. This feature extends the same capability to blog posts.
Image Types
Hero Image
- One per blog post
- Used as the WordPress featured image (
featured_mediain WP REST API) - Also used as the Open Graph image for social sharing previews
- Dimensions: 1200 × 630px (16:9, Open Graph standard)
- Inserted at the top of the blog post Markdown as

Inline Section Images
- Optional; one per major section (H2)
- Used to break up long sections and illustrate key concepts
- Dimensions: 800 × 450px (16:9)
- Inserted immediately after the H2 heading as

Image Style Options
Tenants configure a default image style in Settings → Brand → Content Images. Can be overridden per activity.
| Style | Description | Best for |
|---|---|---|
photo_realistic | Photorealistic scene or object, professional photography aesthetic | B2B services, local businesses |
flat_illustration | Flat 2D illustration, modern and clean, minimal shadows | SaaS, tech, marketing |
minimal_infographic | Simple diagram or icon composition, white background | Educational/how-to content |
abstract | Abstract shapes and gradients in brand colours | Brand awareness content |
Brand Colour Injection
The image prompt includes the tenant’s primary and secondary brand colours from BrandAsset:
const colorInstruction = brandAsset
? `Primary brand colour: ${brandAsset.primaryColor}. Secondary colour: ${brandAsset.secondaryColor}. Incorporate these colours prominently in the composition.`
: '';The style + brand colours are combined with a content-derived description prompt:
const imagePrompt = [
`Style: ${styleDescription[imageStyle]}`,
colorInstruction,
`Subject: ${subjectPrompt}`, // derived from post title + primary keyword
`Format: wide banner, 16:9 aspect ratio, no text overlays`,
].filter(Boolean).join('. ');Input Contract
interface BlogImageGeneratorInput {
tenantId: string;
blogPostId: string;
imageType: 'hero' | 'inline_section';
sectionTitle?: string; // H2 heading text — required for inline_section type
// Derived from BlogPost
postTitle: string;
primaryKeyword: string;
// Style configuration
imageStyle: 'photo_realistic' | 'flat_illustration' | 'minimal_infographic' | 'abstract';
// Brand colours from BrandAsset (optional; graceful fallback if missing)
primaryColor?: string; // hex, e.g. "#1A73E8"
secondaryColor?: string;
}Output Contract
interface BlogImageGeneratorOutput {
tenantId: string;
blogPostId: string;
imageType: 'hero' | 'inline_section';
sectionTitle?: string;
imageUrl: string; // DigitalOcean Spaces public URL
altText: string; // auto-generated descriptive alt text for accessibility
width: number;
height: number;
}How It Works
Blog post reaches dm_approved status (optional trigger) OR
DM manually clicks "Generate Images" on blog detail page
↓
API: POST /tenant/v1/blog/:id/generate-images
Payload: { imageTypes: ['hero', 'inline_sections'], imageStyle, sectionTitles? }
↓
Enqueue blog-image-generator job(s):
- One job for hero image
- One job per section with an H2 title (if inline_sections requested)
↓
blog-image-generator.worker.ts:
1. Construct image prompt from post title + keyword + style + brand colours
2. Call Azure OpenAI Images.generate (dall-e-3 model, 1024×1024, quality: standard)
3. Resize to target dimensions using sharp (1200×630 for hero, 800×450 for inline)
4. Upload to DigitalOcean Spaces: tenants/{tenantId}/blog/{blogPostId}/{imageType}.jpg
5. Return { imageUrl, altText }
↓
API inserts image URLs into BlogPost.bodyMarkdown:
- Hero: prepend to body
- Inline: insert after each corresponding H2
↓
If WordPress is connected and post is already published:
For hero: PATCH /wp-json/wp/v2/posts/{wpPostId} with featured_media set
For inline: update post content via WP API with new Markdown-to-blocks conversion
↓
Dashboard blog editor refreshes to show images in previewWordPress Featured Image
When a hero image is generated for a post that is already published to WordPress:
- Upload the image to WordPress Media Library via
POST /wp-json/wp/v2/media - Set as
featured_mediaviaPATCH /wp-json/wp/v2/posts/{wpPostId}
This requires the provider-wordpress package to expose a setFeaturedImage() method.
Auto-Generate on Publish (Optional Tenant Setting)
Tenants can enable “Auto-generate hero image on blog publish” in Settings → Brand → Content Images. When enabled:
BlogPost transitions to published
↓
Activity Planner checks: auto_generate_hero_image setting = true?
↓
If yes: enqueue blog-image-generator job (hero type only)
↓
Image generated + inserted into postCredit deducted (0.5 cr) before job is enqueued. If insufficient credits, image generation is skipped with a dashboard warning.
Dashboard UI
Blog Post Detail Page:
- “Images” section in the blog editor sidebar
- Hero image: preview thumbnail + “Regenerate” button + style selector
- Inline images: list of H2 sections with “Generate” toggle per section
- “Generate All” button: generates hero + all inline sections in parallel
- Image status: Generating (spinner) → Done (thumbnail) → Failed (retry button)
Image Style Settings:
- Settings → Brand → Content Images
- Default style selector (photo_realistic / flat_illustration / minimal_infographic / abstract)
- Preview: shows a sample generated image in the selected style with current brand colours
- Per-activity style override available in blog activity creation form
Key Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
Separate worker from social-post-designer | New blog-image-generator worker | Blog images need different dimensions, different prompt structure, and inline insertion logic |
Resize with sharp | Resize after generation (1024×1024 → target) | GPT Image standard size is 1024×1024; resizing in worker is cheaper than requesting custom sizes |
| Store in DigitalOcean Spaces | Same bucket as social post designer images | Consistent storage pattern; existing upload utility reused |
| Insert into Markdown | Direct Markdown  insertion | Blog posts are Markdown throughout the pipeline; no separate image reference model needed |
| WordPress sync is optional | Only update WP if post is already published | Avoids race conditions where image is generated before WordPress publish completes |
Implementation Phases
Phase 1 — Hero Image
- Create
docs/agents/blog-image-generator.md(agent doc) - Add
blog-image-generatortoAgentRoletype union - Create
packages/agents/src/workers/blog-image-generator.worker.ts - Add
resizestep usingsharp(already in monorepo for social post designer) POST /tenant/v1/blog/:id/generate-imagesroute (hero only)- Dashboard blog detail: “Generate Hero Image” button + preview
Phase 2 — Inline Section Images
- Extend worker to handle
inline_sectiontype - H2 section parser: extract section titles from
BlogPost.bodyMarkdown - Parallel job enqueueing for multiple inline images
- Insert image Markdown after corresponding H2 in
bodyMarkdown
Phase 3 — WordPress Sync
- Add
setFeaturedImage()toprovider-wordpress - After hero image generation, if post is published to WordPress: upload to WP media + set as featured_media
- After inline image generation for published post: update WP post content
Phase 4 — Auto-Generate + Settings
- Tenant settings: “Auto-generate hero image on blog publish” toggle
- Image style settings page with brand-colour preview
- Per-activity style override in blog activity creation form
- Auto-generate trigger hooked to
publishedstatus transition