How-to

Create Open Graph images programmatically

If every blog post or docs page needs a unique social card, hand-designing PNGs does not scale. Call an image API with title + subtitle, store the PNG (or URL), and point og:image at it.

What “good enough” looks like

Template studios (Bannerbear, Placid, Templated) shine when designers own layouts. For title-driven cards, a thin API with forever credits is usually enough.

1. One-shot with curl

curl -X POST https://ogstamp.com/api/og \
  -H "Authorization: Bearer ogs_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Ship the changelog","subtitle":"Week 12","theme":"coral"}' \
  -o og.png

Free tier (no key): 30 renders / IP / day via GET — fine for trying themes. Production volume needs a key from pricing.

2. Node / edge fetch

const res = await fetch("https://ogstamp.com/api/og", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.OGSTAMP_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: post.title,
    subtitle: post.excerpt?.slice(0, 120) || "",
    theme: "forest",
  }),
});
if (!res.ok) throw new Error(await res.text());
const bytes = Buffer.from(await res.arrayBuffer());
// write to R2 / S3 / public/og/{slug}.png

3. Next.js metadata (proxy the key)

Do not put the API key in client components. Generate at build or in a server route, then reference your own CDN URL:

// app/api/og-proxy/route.ts — server only
export async function GET(req: Request) {
  const title = new URL(req.url).searchParams.get("title") || "Post";
  const upstream = await fetch("https://ogstamp.com/api/og", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.OGSTAMP_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ title, theme: "slate", logo_url: "https://yoursite.com/logo.png" }),
  });
  return new Response(upstream.body, {
    headers: { "Content-Type": "image/png", "Cache-Control": "public, max-age=86400" },
  });
}

Prefer writing the PNG to object storage and using that URL in metadata. Free GET /api/og is for experiments only (30/IP/day).

Framework notes: Next.js, Astro, Hugo.

4. HTML meta tags

<meta property="og:image" content="https://cdn.example.com/og/ship-changelog.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />

More on tags: meta OG tags. Size cheat sheet: OG image size.

5. Caching and cost

Generate once per content revision. Hash title|subtitle|theme into the filename so republishing the same post does not burn credits. Starter is $9 for 500 forever images — quieter than a $19–$49/mo template plan when volume is spiky. Compare Bannerbear and Placid.