How to Generate Open Graph Images with a Screenshot API
When you share a link on Twitter, LinkedIn, or Slack, the preview card is powered by Open Graph (OG) images. Most developers either use static images (boring) or paid services like Cloudinary or Vercel's OG image generation (expensive at scale).
There's a simpler approach: use a screenshot API to capture a styled HTML page and use the result as your OG image.
The Problem with Static OG Images
Static OG images don't change when your content changes. A blog post titled "Q1 Results" shows the same generic preview whether the results are good or bad. Dynamic OG images solve this by generating a fresh image for each page.
How It Works
- Create an HTML template with your title, author, and branding
- Host it at a URL with query parameters (e.g.,
/og?title=Hello+World) - Call the screenshot API to capture it
- Serve the result as your OG image
Step 1: Build an OG Template
Create a simple HTML page designed at 1200x630 pixels (the standard OG image size):
<!DOCTYPE html>
<html>
<head>
<style>
body {
width: 1200px;
height: 630px;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
font-family: -apple-system, sans-serif;
color: white;
}
.card {
text-align: center;
padding: 60px;
}
h1 { font-size: 56px; margin-bottom: 20px; }
p { font-size: 24px; opacity: 0.8; }
</style>
</head>
<body>
<div class="card">
<h1>Your Article Title Here</h1>
<p>yoursite.com</p>
</div>
</body>
</html>
Step 2: Make It Dynamic
Add query parameter handling so each page gets a unique image:
<script>
const params = new URLSearchParams(window.location.search);
document.querySelector('h1').textContent = params.get('title') || 'Default Title';
document.querySelector('p').textContent = params.get('site') || 'yoursite.com';
</script>
Now /og-template?title=My+Post&site=myblog.com renders a custom OG image.
Step 3: Capture with the Screenshot API
curl "https://hermesforge.dev/api/screenshot?\
url=https://yoursite.com/og-template?title=My+Post\
&width=1200&height=630&format=png"
The API returns a 1200x630 PNG — exactly the right size for OG images.
Python automation for multiple pages:
import requests
import urllib.parse
API = "https://hermesforge.dev/api/screenshot"
def generate_og_image(title, output_path):
"""Generate an OG image for a given title."""
template_url = f"https://yoursite.com/og?title={urllib.parse.quote(title)}"
response = requests.get(API, params={
"url": template_url,
"width": 1200,
"height": 630,
"format": "png"
})
with open(output_path, "wb") as f:
f.write(response.content)
print(f"Generated: {output_path} ({len(response.content)} bytes)")
# Generate OG images for all your blog posts
posts = [
("How to Build a REST API", "og-rest-api.png"),
("Python Tips for 2026", "og-python-tips.png"),
("Why GraphQL Won", "og-graphql.png"),
]
for title, filename in posts:
generate_og_image(title, filename)
Step 4: Use Custom JavaScript for Extra Control
The screenshot API supports custom JavaScript injection, which means you can modify the template page before capture:
curl "https://hermesforge.dev/api/screenshot?\
url=https://yoursite.com/og-template\
&width=1200&height=630\
&js=document.querySelector('h1').textContent='Custom Title';\
document.body.style.background='linear-gradient(135deg,%23f093fb,%2300d2ff)'"
This lets you use a single static template and customize it entirely via the API call — no server-side rendering needed.
Why This Beats Alternatives
| Approach | Cost | Dynamic | Setup |
|---|---|---|---|
| Static images | Free | No | Manual |
| Cloudinary | $89+/mo | Yes | Complex |
| Vercel OG | Free tier limits | Yes | Framework-specific |
| Screenshot API | Free (rate limited) | Yes | Any HTML |
The screenshot API approach works with any HTML — no framework lock-in, no vendor-specific syntax. If you can build a webpage, you can generate OG images.
Best Practices
- Cache aggressively: OG images don't need to change often. Generate once and cache.
- Use WebP for file size: Add
&format=webpto reduce image size by ~49% compared to PNG. - Block ads: Add
&block_ads=trueto prevent any ad scripts from affecting your template. - Set exact dimensions: Always specify
&width=1200&height=630for standard OG sizing. - Test with validators: Use Facebook's Sharing Debugger or Twitter Card Validator to verify your images.
Getting Started
The screenshot API is free to use with no signup required:
https://hermesforge.dev/api/screenshot?url=YOUR_URL&width=1200&height=630
For higher rate limits, request a free API key — it takes 10 seconds.
This approach works for blogs, SaaS landing pages, documentation sites, or any project that shares links on social media. One API call per page, cached indefinitely, zero design tools required.