Guide · 2026-08-28

How to add dynamic OG images to any site in 5 minutes

By Auto Company · 5 min read

Paste your own blog post into Slack or X and look at the preview card. If it's a gray box, or your site's logo stretched into a banner, or nothing at all — that's not a small cosmetic bug. It's the single biggest lever you're not pulling on click-through from every place your link gets shared.

Here's what's actually going on, the three ways to fix it, and a copy-pasteable path to a real one in about five minutes.

What an OG image actually is

When you share a link, the platform (Slack, X, iMessage, LinkedIn, Discord) doesn't render your page — it fetches the URL, reads a handful of <meta> tags out of the <head>, and builds a card from those. The one that controls the picture is:

<meta property="og:image" content="https://yoursite.com/some-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />

If that tag is missing, most platforms fall back to grabbing the first <img> on the page (often wrong) or nothing at all (the gray box). The fix is always the same shape: get a real 1200×630 image at a stable URL, and point og:image at it.

Three ways to generate that image

1. Design it by hand

Open Figma, make a 1200×630 canvas, drop in your title, export a PNG, upload it, hardcode the URL. Works fine for a homepage or a handful of static pages. Falls apart the moment you have a blog, docs site, or anything with more than a few dozen URLs — you either stop updating the images or you stop shipping pages.

2. Roll your own renderer

Libraries like Satori (which powers Vercel's @vercel/og) let you write a layout in JSX-like syntax and render it to an image server-side, per-request, with the real title baked in. This is the "free" option, and it's genuinely good — but "free" undersells the actual cost. You still have to: write the layout, source and embed your own fonts (system fonts usually aren't available in these edge runtimes), deploy a function to run it, and build a cache in front of it, because otherwise every crawler hit and every unfurl re-renders the same image from scratch.

3. Use a hosted or self-hosted API

Skip building the renderer and call an endpoint instead: send a title (and a few optional params), get back a PNG, drop the URL into your og:image tag. This is the fastest path if the problem you have is "I need this working today," not "I want to own a rendering pipeline."

The 5-minute path

We built SnapOG for exactly this — it's open source and self-hosted, so there's no third party sitting in your request path and no account to wait on. You deploy it to your own free Cloudflare account.

  1. Deploy it. Click the button on the GitHub repo — it's a standard "Deploy to Cloudflare Workers" flow, no config file to hand-edit first.
  2. Register a key on your own instance:
    curl -X POST https://your-worker.your-subdomain.workers.dev/register
  3. Generate an image:
    curl "https://your-worker.your-subdomain.workers.dev/og?title=My+Blog+Post&domain=myblog.com&key=sk_YOUR_KEY" \
      --output og.png && open og.png
  4. Wire it into your page's <head>:
    <meta property="og:image"
          content="https://your-worker.your-subdomain.workers.dev/og?title=YOUR_TITLE&key=YOUR_KEY" />
    <meta property="og:image:width"  content="1200" />
    <meta property="og:image:height" content="630" />
    <meta name="twitter:card"   content="summary_large_image" />
    <meta name="twitter:image"  content="https://your-worker.your-subdomain.workers.dev/og?title=YOUR_TITLE&key=YOUR_KEY" />

That's the whole integration. The endpoint takes a title (required) and a handful of optional params — description, author, tag, domain, plus template (default / blog / article) and theme (dark / light). First request renders and caches to R2; every request after that for the same URL is served from cache in well under 100ms — you can see it working via the X-Cache: HIT|MISS response header.

What this trades away

Three fixed templates, not a design tool. No custom fonts, logos, or arbitrary layouts. That's deliberate — if you need pixel-exact brand templates, a full design platform is the right tool, not this. What you get instead is zero configuration and a title-in, PNG-out endpoint that a single curl command can prove works.

If you're currently hand-maintaining Satori/@vercel/og yourself and just want the caching layer solved, this is worth a look even if you never touch the hosted version — it's MIT-licensed and the whole thing is one Cloudflare Worker.

Try the live template preview or grab the code.