How to Auto-Generate OG Images for Every Blog Post in 2026

May 7, 2026 · 9 min read · By TinyTools

You launch a blog. The first ten posts get hand-designed OG images in Figma — beautiful, on-brand, exactly 1200 × 630. Then post eleven needs to ship in twenty minutes and you skip it. Now half your archive shares as a generic gray fallback on Slack and LinkedIn, and your social CTR for those posts is roughly nothing.

The fix is to stop designing OG images and start generating them. Pick a template, define the variables (title, author, tag, accent color), and have your build pipeline or a CDN URL produce the final 1200 × 630 PNG every time you publish. This is a 2026 walkthrough of the four approaches that actually work, when each one wins, and copy-paste code for the two most common stacks.

The four approaches, ranked by friction

Every auto-OG setup falls into one of four buckets. Pick by stack and traffic, not by what's trendy.

ApproachBest forSetup timePer-image cost
@vercel/og (Satori)Next.js, Astro, Remix, Nuxt sites already on Vercel/Cloudflare~30 min$0 (within free edge limits)
Cloudinary URL transformsAnything with a CDN already and no custom rendering needs~15 min$0–$0.005
Bannerbear / Placid APIBrand-heavy designs, non-developer teams, multi-template per post~1 hour$0.01–$0.05
Template + spreadsheet loopStatic blogs, archives, no infra appetite~2 hours once$0

If you're on a JavaScript framework that deploys to an edge platform, @vercel/og is almost always the right answer. If you're on WordPress or Ghost, Cloudinary URL transforms are the path of least resistance. If you have a designer on the team, Bannerbear lets them own the template and developers just call an API. If you have neither and just need to backfill an archive once, the template-loop approach is the cheapest hack.

Approach 1: @vercel/og (Satori) — the default for JS frameworks

@vercel/og is a small library that renders React/JSX to a PNG via Satori, Vercel's HTML-to-SVG renderer. It runs on the edge runtime, returns an image in 50–200ms, and ships with most modern Next.js starter templates.

The complete setup for a Next.js 15 App Router blog:

// app/og/route.tsx
import { ImageResponse } from 'next/og';

export const runtime = 'edge';

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const title = searchParams.get('title')?.slice(0, 100) ?? 'Untitled';
  const tag = searchParams.get('tag') ?? 'Blog';

  return new ImageResponse(
    (
      <div style={{
        height: '100%', width: '100%', display: 'flex', flexDirection: 'column',
        background: 'linear-gradient(135deg, #0a0a0f 0%, #15151f 100%)',
        padding: '80px', justifyContent: 'space-between', color: 'white',
        fontFamily: 'Inter, sans-serif',
      }}>
        <div style={{ fontSize: 24, color: '#06b6d4', fontWeight: 600 }}>
          {tag.toUpperCase()}
        </div>
        <div style={{
          fontSize: 72, fontWeight: 800, lineHeight: 1.1,
          letterSpacing: '-0.02em', maxWidth: '90%',
        }}>
          {title}
        </div>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <div style={{ fontSize: 28, fontWeight: 700 }}>yourblog.com</div>
          <div style={{ fontSize: 22, color: '#a0a0b8' }}>@yourbrand</div>
        </div>
      </div>
    ),
    { width: 1200, height: 630 }
  );
}

Then in your post layout:

<meta property="og:image"
  content={`https://yourblog.com/og?title=${encodeURIComponent(post.title)}&tag=${post.category}`} />

That's the entire integration. Every post URL gets a unique OG image, generated on-demand at the edge, cached by Vercel's CDN forever (or until the URL params change). You'll want to add a custom font for production — Satori uses a default font that's serviceable but not beautiful. Loading Inter from Google Fonts adds about 30 lines and another 50ms to the cold start.

Things to know before you ship @vercel/og

Approach 2: Cloudinary URL transforms — the WordPress / Ghost path

If you're on a CMS that doesn't run JavaScript on the edge, Cloudinary's URL-based image transformations let you compose an OG image from a base template plus text overlays, all encoded in the URL. No build step, no API call, no library.

Upload a base template once (a 1200 × 630 PNG with your brand bar, logo, and an empty headline area). Then any URL like:

https://res.cloudinary.com/yourcloud/image/upload/
  w_1100,c_fit,co_white,g_west,x_50,y_-50,
  l_text:Inter_72_bold:Why%20Static%20Sites%20Win%20in%202026/
  v1/og-template.png

...renders the template with that headline burned in. Cloudinary caches at the URL level, so every post becomes effectively static after first render.

The pattern in a WordPress functions.php:

function dynamic_og_image() {
  if (!is_single()) return;
  $title = rawurlencode(get_the_title());
  $url = "https://res.cloudinary.com/yourcloud/image/upload/" .
         "w_1100,c_fit,co_white,g_west,x_50,y_-50," .
         "l_text:Inter_72_bold:{$title}/v1/og-template.png";
  echo "<meta property='og:image' content='{$url}' />";
}
add_action('wp_head', 'dynamic_og_image');

Ten minutes of work, every post gets a unique OG image, costs land in Cloudinary's free tier for any blog under ~25k monthly shares.

Approach 3: Bannerbear / Placid — when designers own the template

For brands that want pixel-perfect, on-brand OG images that change layout per content type (interview cards look different from how-to cards look different from changelog cards), an API service like Bannerbear or Placid lets your designer build templates in a visual editor and your developer hits a JSON endpoint:

POST https://api.bannerbear.com/v2/images
{
  "template": "abc123",
  "modifications": [
    { "name": "headline", "text": post.title },
    { "name": "author", "text": post.author },
    { "name": "category", "text": post.category, "color": colorMap[post.category] }
  ]
}

The response includes a CDN URL you store in the post's frontmatter. Generation runs ~1–3 seconds on first request and is cached after. Cost is $0.01–$0.05 per image at typical plans, which is irrelevant for blogs but adds up if you're generating thousands per day.

Worth it when you have a designer who wants ownership of the OG visual identity without learning React. Overkill if you're a one-person blog.

Approach 4: Template + spreadsheet — the no-code archive backfill

Got 80 old posts that ship without OG images? Don't engineer a system. Open a spreadsheet, list every post slug + title, paste those into a free OG image template once each, download the PNGs, and upload them to your media folder. Then update each post's OG meta tag.

This is the right move when:

For volumes above ~150 posts, the spreadsheet approach starts to chafe and one of the first three approaches will pay back its setup cost within an afternoon.

Need a template to start from? Generate one in 30 seconds.

Eight pre-tested OG templates at exactly 1200 × 630, safe-zone padding built in, every color palette tested for contrast on Slack white and LinkedIn blue. Free, no signup, downloads as PNG. Use it as your base template for any of the approaches above.

Open the OG Image Generator →

The decision matrix in one paragraph

On a JS framework already deploying to Vercel or Cloudflare? Use @vercel/og — it'll be working before lunch and will scale forever for free. On WordPress, Ghost, or any CMS? Use Cloudinary URL transforms — same idea, no infra. Have a designer who needs to own the visual? Buy Bannerbear, save the API call, ship faster than building it yourself. Backfilling an archive once with no infra appetite? Open a generator, run a spreadsheet loop, move on. The wrong move is hand-designing every post in Figma — that's the system that quietly breaks the moment publishing volume goes up.

The five rules that apply to every approach

  1. Always 1200 × 630. Same canvas every time. Don't try to be clever with platform-specific variants — designing for Slack and iMessage covers everything else automatically.
  2. Headline ≤ 60 characters on the image. Even if your post title is longer, truncate or rewrite for the image. Anything more turns into unreadable mush at thumbnail size.
  3. Set og:image:width and og:image:height explicitly. Saves the platform a round trip and unlocks the large card preview.
  4. Keep PNGs under 300KB. iOS 19+ silently drops oversized OG images. Lean on Squoosh or sharp's encoder if your generated PNGs are heavy.
  5. Validate after publishing. Hit X's card validator and LinkedIn's Post Inspector once. If they cache a broken image, it'll be wrong for a week.

What this unlocks

The compounding benefit isn't per-post CTR — it's the fact that every old post becomes shareable retroactively. A blog that ships 50 posts a year without OG images leaves probably 60–80% of its potential social traffic on the table. Setting up auto-generation once captures that traffic on every future post AND on every backfilled archive post in the same afternoon. There aren't many wins in 2026 SEO that take an afternoon and pay back forever — this is one of them.

Pick the approach that matches your stack, ship the integration today, then come back tomorrow and backfill your archive. The whole project is a weekend at most. The traffic uplift compounds for years.

Generate a perfectly-sized base template in 30 seconds

Use it as the static base for Cloudinary, the design reference for @vercel/og, or the template inside Bannerbear. 1200 × 630, safe zones built in, downloads as PNG.

Try it free →