Automate Social Media OG Images with a Screenshot API

2026-04-14 | Tags: [screenshot-api, og-image, social-media, automation, seo]

Automate Social Media OG Images with a Screenshot API

Every time someone shares your link on Twitter, LinkedIn, or Facebook, the platform looks for an og:image meta tag. No image? Your link gets a plain text card that nobody clicks. A good OG image can double your click-through rate — but creating one for every page is tedious.

Here's a better approach: automatically screenshot your own pages at OG dimensions and serve the result as your og:image.

The Concept

Instead of designing OG images in Figma for every blog post, product page, or landing page:

  1. Design your page to look good at 1200x630 (the OG standard)
  2. Screenshot it at those exact dimensions via API
  3. Serve the screenshot URL as your og:image

The page is the image. Any time you update the page, the image updates too.

Quick Implementation

Static Sites (Build Time)

Generate OG images during your build step:

import requests
import os

API = "https://hermesforge.dev/api/screenshot"
SITE = "https://yoursite.com"
OUTPUT_DIR = "public/og"

os.makedirs(OUTPUT_DIR, exist_ok=True)

pages = [
    "/",
    "/about",
    "/blog/my-first-post",
    "/pricing",
]

for page in pages:
    url = f"{SITE}{page}"
    params = {
        "url": url,
        "viewport": "og",       # 1200x630
        "format": "png",
        "block_ads": "true",
        "delay": "2000",         # Wait for fonts/images
    }
    resp = requests.get(API, params=params)
    resp.raise_for_status()

    slug = page.strip("/").replace("/", "-") or "index"
    path = f"{OUTPUT_DIR}/{slug}.png"
    with open(path, "wb") as f:
        f.write(resp.content)
    print(f"Generated OG image: {path} ({len(resp.content) // 1024}KB)")

Then in your HTML templates:

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

Dynamic Sites (On Demand)

For sites with many pages, generate OG images on the fly:

// Express middleware
app.get('/og-image/*', async (req, res) => {
  const pagePath = req.params[0] || '';
  const pageUrl = `https://yoursite.com/${pagePath}`;

  const apiUrl = new URL('https://hermesforge.dev/api/screenshot');
  apiUrl.searchParams.set('url', pageUrl);
  apiUrl.searchParams.set('viewport', 'og');
  apiUrl.searchParams.set('format', 'png');
  apiUrl.searchParams.set('block_ads', 'true');

  const response = await fetch(apiUrl);
  if (!response.ok) {
    return res.status(502).send('OG image generation failed');
  }

  res.set('Content-Type', 'image/png');
  res.set('Cache-Control', 'public, max-age=86400'); // Cache 24h
  const buffer = await response.arrayBuffer();
  res.send(Buffer.from(buffer));
});

Then reference it dynamically:

<meta property="og:image" content="https://yoursite.com/og-image/blog/my-post">

Platform-Specific Images

Different platforms have different optimal dimensions. Use viewport presets to generate each:

platforms = {
    "og": "og",           # 1200x630 — Facebook, general
    "twitter": "twitter",  # 1200x675 — Twitter/X cards
    "linkedin": "linkedin", # 1200x627 — LinkedIn posts
}

for platform, viewport in platforms.items():
    params = {
        "url": page_url,
        "viewport": viewport,
        "format": "png",
        "block_ads": "true",
    }
    resp = requests.get(API, params=params)
    with open(f"og/{slug}-{platform}.png", "wb") as f:
        f.write(resp.content)

Then use platform-specific meta tags:

<!-- Facebook / Default -->
<meta property="og:image" content="https://yoursite.com/og/post-og.png">

<!-- Twitter -->
<meta name="twitter:image" content="https://yoursite.com/og/post-twitter.png">
<meta name="twitter:card" content="summary_large_image">

Crop the Best Part with Clip

Sometimes the full viewport isn't ideal for OG. Use the clip parameter to capture just the hero section:

# Capture only the top 630px of a 1200px-wide page
curl "https://hermesforge.dev/api/screenshot?url=https://yoursite.com&width=1200&clip=0,0,1200,630&format=png" \
  -o og-image.png

This is perfect when your page has a strong hero section but the rest doesn't look good at OG dimensions.

CI/CD Integration

Regenerate OG images on every deploy:

# GitHub Actions
name: Generate OG Images
on:
  push:
    branches: [main]
    paths: ['content/**', 'src/pages/**']

jobs:
  og-images:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install requests
      - run: python scripts/generate_og_images.py
      - uses: actions/upload-artifact@v4
        with:
          name: og-images
          path: public/og/

Tips

  1. Add delay=2000 — Fonts, images, and CSS animations need time to load. 2 seconds is usually enough.
  2. Use block_ads=true — Cookie banners and ad overlays ruin OG images.
  3. Cache aggressively — OG images don't change often. Cache for 24 hours minimum.
  4. Use PNG, not WebP — Social platforms handle PNG more reliably than WebP for og:image.
  5. Design for 1200x630 — Make sure your page's above-the-fold content looks good at exactly these dimensions. Preview with viewport=og.
  6. Test with social debuggers — Facebook Sharing Debugger, Twitter Card Validator, and LinkedIn Post Inspector all let you preview how your link will look.

Why Not Use a Template?

Template-based OG image generators (like @vercel/og) work well for simple text+logo images. But they can't capture:

Screenshot-based OG images show the actual page. What you see is what gets shared.