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
- 1200×630 PNG (Open Graph / Twitter large card)
- Readable title at small preview sizes
- Stable URL or hashed filename so crawlers can cache
- Key never exposed in the browser
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.