Back to blog

OG images

How to Generate Dynamic OG Images from Any URL

June 12, 2026 · 4 min read · Grabbit Team

Most sites share links with a broken card or a generic company logo. The pages that get clicks have a sharp 1200 by 630 image that shows the post title and brand. Designing that image in a tool does not scale past a handful of pages. The approach that does: one HTML template, captured by an API, cached forever.

Why a template-and-capture approach

A dynamic OG image has the same data as the page it represents: a title, maybe a description or author. If you can render that data in HTML, a screenshot API can turn it into an image. The template lives in your codebase, the API handles the browser, and you never open a design tool.

This pattern also means a single CSS change to the template updates every OG image across the site. No re-exporting, no version drift.

Step 1: Build the template page

Create a route at /og that reads query parameters and renders a styled card at 1200 by 630. A minimal server-rendered example in any framework looks like this:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <style>
      * { box-sizing: border-box; margin: 0; padding: 0; }
      body {
        width: 1200px;
        height: 630px;
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 80px;
        background: #0f172a;
        font-family: system-ui, sans-serif;
      }
      .eyebrow { color: #60a5fa; font-size: 24px; margin-bottom: 24px; }
      .title   { color: #f8fafc; font-size: 68px; font-weight: 700; line-height: 1.1; }
      .logo    { position: absolute; bottom: 48px; right: 80px; color: #94a3b8; font-size: 20px; }
    </style>
  </head>
  <body>
    <div class="eyebrow">{{ category }}</div>
    <div class="title">{{ title }}</div>
    <div class="logo">yoursite.com</div>
  </body>
</html>

Swap the Mustache-style placeholders for whatever your server framework uses. Keep the body fixed at 1200 by 630 so the viewport matches the capture size exactly. Add your actual brand colors, a logo SVG, or a background gradient, then preview the page in a browser to confirm it looks right before automating the capture.

Step 2: Capture the template with a screenshot API

Once the template is live at a public URL, generating an image is one POST request:

curl https://grabbit.live/api/v1/grabs \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yoursite.com/og?title=How+to+Build+a+Screenshot+API&category=Guides",
    "width": 1200,
    "height": 630,
    "format": "webp"
  }'

The response includes a hosted image_url you can drop directly into your og:image tag:

{
  "id": "grb_01jx...",
  "status": "done",
  "image_url": "https://cdn.grabbit.live/grabs/grb_01jx....webp",
  "width": 1200,
  "height": 630,
  "format": "webp",
  "bytes": 38420,
  "execution_ms": 910
}

Use "format": "webp" for smaller file sizes. If your template renders text via JavaScript instead of server-side, add "delay_ms": 500 to give the script time to run before the capture fires.

Making it dynamic

The URL is the variable part. For a blog, construct the template URL per post and call the API once at publish time or on the first request:

async function generateOgImage(title: string, category: string): Promise<string> {
  const templateUrl = new URL('https://yoursite.com/og');
  templateUrl.searchParams.set('title', title);
  templateUrl.searchParams.set('category', category);

  const res = await fetch('https://grabbit.live/api/v1/grabs', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GRABBIT_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      url: templateUrl.toString(),
      width: 1200,
      height: 630,
      format: 'webp',
    }),
  });

  const grab = await res.json();
  return grab.image_url; // store this; don't call the API again on repeat visits
}

Store the returned image_url in your CMS, database, or a build-time JSON file. On every subsequent request, serve the stored URL. The API call runs once per unique image.

Waiting for JavaScript-rendered content

If your template depends on a framework that hydrates after the initial HTML is served, the screenshot may fire before the text is visible. Two options:

  • Fixed delay: "delay_ms": 800 waits that many milliseconds after the page loads.
  • Element selector: "selector": "#title" waits until that element is present in the DOM before capturing. This is more reliable than a fixed delay because it ties the capture to a real content signal rather than a guess.

For a pure server-rendered template, neither option is needed.

Caching and the og:image meta tag

Once you have the image_url, wire it into your page head:

<meta property="og:image" content="https://cdn.grabbit.live/grabs/grb_01jx....webp" />
<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://cdn.grabbit.live/grabs/grb_01jx....webp" />

Set og:image:width and og:image:height so platforms reserve the right space without fetching the image first. For the twitter:card value, summary_large_image is the one that renders the big preview on X.

Wrapping up

The full workflow: build one HTML template, pass page data as query parameters, call the Grabbit screenshot API once, store the image_url, and serve it from your og:image tag. Update the template design once and every image refreshes on the next capture.

For exact OG image dimensions and what each platform expects, see Open Graph image sizes and dimensions.

FAQ

What is an OG image generator?
An OG image generator turns a URL or HTML template into a 1200 by 630 pixel image that social platforms display when the link is shared. The most maintainable approach is a screenshot API: build one HTML template, pass page-specific data as query parameters, and capture it at the right dimensions on demand.
How do I generate OG images automatically for every page?
Build a single HTML template that accepts a title and other fields as URL query parameters, then call a screenshot API each time a new page is created or first requested. Store the returned image URL in your CMS or database so subsequent requests serve the cached result without another API call.
What is the best size for a generated OG image?
1200 by 630 pixels. This is the 1.91 to 1 ratio that Facebook, X, LinkedIn, Slack, and Discord all render as a full-width preview card. Set og:image:width and og:image:height in your meta tags so platforms reserve the right space immediately.
Can I generate OG images without a design tool?
Yes. Write your template as a regular HTML and CSS page, host it at a route like /og, and pass data as URL query parameters. A screenshot API captures the rendered page and returns a hosted image URL you can use directly as your og:image value.
Does generating an OG image on every request hurt performance?
Not if you cache correctly. Call the screenshot API once per unique set of parameters, store the returned image_url alongside the page record, and serve it from there. The API call only runs on the first request or when the page content changes.

Capture any website with one API call

Get a free test key and capture your first screenshot in two minutes.

Written by

Grabbit Team

Screenshots as a service

The team behind Grabbit, the screenshot API for developers and AI agents. We write about web capture, rendering, and automating screenshots at scale.

Keep reading