Skip to Content
Content ToolkitBlog Image Generation

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

FunctionGenerate brand-matched hero and inline images for blog posts
TypeWorker — Content
StatusTo Build
PriorityP3 — Growth
Queueagent__blog-image-generator
Concurrency2
Timeout5 min
Est. cost / task~$0.08 per hero image (Azure GPT Image standard quality)
Credits0.5 cr hero image · 0.25 cr per inline image
PlanPro+

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:

  1. Visual quality: WordPress posts without a featured image show a blank thumbnail in post lists and social sharing previews (Open Graph).
  2. 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_media in 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 ![{alt text}]({imageUrl})

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 ![{alt text}]({imageUrl})

Image Style Options

Tenants configure a default image style in Settings → Brand → Content Images. Can be overridden per activity.

StyleDescriptionBest for
photo_realisticPhotorealistic scene or object, professional photography aestheticB2B services, local businesses
flat_illustrationFlat 2D illustration, modern and clean, minimal shadowsSaaS, tech, marketing
minimal_infographicSimple diagram or icon composition, white backgroundEducational/how-to content
abstractAbstract shapes and gradients in brand coloursBrand 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 preview

When a hero image is generated for a post that is already published to WordPress:

  1. Upload the image to WordPress Media Library via POST /wp-json/wp/v2/media
  2. Set as featured_media via PATCH /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 post

Credit 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

DecisionChoiceRationale
Separate worker from social-post-designerNew blog-image-generator workerBlog images need different dimensions, different prompt structure, and inline insertion logic
Resize with sharpResize after generation (1024×1024 → target)GPT Image standard size is 1024×1024; resizing in worker is cheaper than requesting custom sizes
Store in DigitalOcean SpacesSame bucket as social post designer imagesConsistent storage pattern; existing upload utility reused
Insert into MarkdownDirect Markdown ![alt](url) insertionBlog posts are Markdown throughout the pipeline; no separate image reference model needed
WordPress sync is optionalOnly update WP if post is already publishedAvoids race conditions where image is generated before WordPress publish completes

Implementation Phases

Phase 1 — Hero Image

  1. Create docs/agents/blog-image-generator.md (agent doc)
  2. Add blog-image-generator to AgentRole type union
  3. Create packages/agents/src/workers/blog-image-generator.worker.ts
  4. Add resize step using sharp (already in monorepo for social post designer)
  5. POST /tenant/v1/blog/:id/generate-images route (hero only)
  6. Dashboard blog detail: “Generate Hero Image” button + preview

Phase 2 — Inline Section Images

  1. Extend worker to handle inline_section type
  2. H2 section parser: extract section titles from BlogPost.bodyMarkdown
  3. Parallel job enqueueing for multiple inline images
  4. Insert image Markdown after corresponding H2 in bodyMarkdown

Phase 3 — WordPress Sync

  1. Add setFeaturedImage() to provider-wordpress
  2. After hero image generation, if post is published to WordPress: upload to WP media + set as featured_media
  3. After inline image generation for published post: update WP post content

Phase 4 — Auto-Generate + Settings

  1. Tenant settings: “Auto-generate hero image on blog publish” toggle
  2. Image style settings page with brand-colour preview
  3. Per-activity style override in blog activity creation form
  4. Auto-generate trigger hooked to published status transition

© 2026 Leadmetrics — Internal use only