Generate Dynamic Open Graph Images with a Screenshot API
February 9, 2026 ยท 6 min read
Photo via Unsplash
When someone shares your page on Twitter, LinkedIn, Slack, or Discord, the link preview image is the first thing people see. A well-designed Open Graph image can dramatically increase click-through rates. But creating unique OG images for every blog post, product page, or user profile manually is impractical. The solution: generate them dynamically from HTML templates using a screenshot API.
What Are Open Graph Images?
Open Graph (OG) images are the preview images that appear when a URL is shared on social platforms. They are defined using meta tags in your HTML:
<meta property="og:image" content="https://yoursite.com/og/my-post.png">
<meta property="og:title" content="My Blog Post Title">
<meta property="og:description" content="A short description of the post">
The recommended dimensions are 1200x630 pixels for maximum compatibility across all platforms. Without a custom OG image, platforms either show a generic icon or a random image from your page, which looks unprofessional and lowers engagement.
Why Dynamic OG Images Matter
- Higher CTR: Posts with custom images get 2-3x more clicks on social platforms
- Brand consistency: Every shared link reinforces your brand design
- Zero manual work: Generate images automatically for thousands of pages
- Always up to date: Images reflect current content, not stale screenshots
The Approach: HTML Template to Image
The workflow is simple:
- Create an HTML/CSS template for your OG image design
- Inject dynamic data (title, author, date, etc.) into the template
- Send the HTML to SnapAPI, which renders it in a real browser and returns a PNG
- Serve or cache the resulting image
Key advantage: Since SnapAPI renders HTML in a real Chromium browser, you get full CSS support -- gradients, custom fonts, flexbox, grid, shadows, and even background images all work perfectly.
Step 1: Design Your HTML Template
Here is a clean, reusable OG image template. Note the exact 1200x630 dimensions:
const template = (title, author, date) => `
<!DOCTYPE html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px;
height: 630px;
display: flex;
flex-direction: column;
justify-content: center;
padding: 80px;
background: linear-gradient(135deg, #0a0a1a 0%, #1a0a2e 50%, #0a1a2e 100%);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
color: white;
}
.logo {
font-size: 24px;
font-weight: 800;
margin-bottom: 40px;
opacity: 0.7;
}
.logo span { color: #00d4ff; }
h1 {
font-size: 56px;
font-weight: 800;
line-height: 1.2;
margin-bottom: 30px;
background: linear-gradient(135deg, #ffffff, #94a3b8);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.meta {
font-size: 20px;
color: #64748b;
}
.meta span { color: #8b5cf6; }
.corner {
position: absolute;
bottom: 40px;
right: 80px;
width: 60px;
height: 60px;
border-radius: 12px;
background: linear-gradient(135deg, #00d4ff, #8b5cf6);
}
</style>
</head>
<body>
<div class="logo">Snap<span>API</span> Blog</div>
<h1>${title}</h1>
<div class="meta">By <span>${author}</span> · ${date}</div>
<div class="corner"></div>
</body>
</html>
`;
Step 2: Generate the Image with Node.js
Send the HTML template to SnapAPI using the html parameter instead of url:
const fs = require("fs");
const SNAPAPI_KEY = "your_api_key";
async function generateOgImage(title, author, date) {
const html = template(title, author, date);
const response = await fetch("https://api.snapapi.pics/v1/screenshot", {
method: "POST",
headers: {
"X-Api-Key": SNAPAPI_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
html: html,
format: "png",
width: 1200,
height: 630,
responseType: "image",
}),
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const buffer = Buffer.from(await response.arrayBuffer());
return buffer;
}
// Generate and save
async function main() {
const image = await generateOgImage(
"How to Build a Screenshot API",
"SnapAPI Team",
"February 9, 2026"
);
fs.writeFileSync("og-image.png", image);
console.log("OG image generated: og-image.png");
}
main();
Step 3: Generate with Python
The same approach works in Python:
import requests
SNAPAPI_KEY = "your_api_key"
def generate_og_image(title: str, author: str, date: str) -> bytes:
html = f"""
<!DOCTYPE html>
<html>
<head>
<style>
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{
width: 1200px;
height: 630px;
display: flex;
flex-direction: column;
justify-content: center;
padding: 80px;
background: linear-gradient(135deg, #0a0a1a 0%, #1a0a2e 50%, #0a1a2e 100%);
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
color: white;
}}
.logo {{ font-size: 24px; font-weight: 800; margin-bottom: 40px; opacity: 0.7; }}
.logo span {{ color: #00d4ff; }}
h1 {{
font-size: 56px;
font-weight: 800;
line-height: 1.2;
margin-bottom: 30px;
}}
.meta {{ font-size: 20px; color: #64748b; }}
.meta span {{ color: #8b5cf6; }}
</style>
</head>
<body>
<div class="logo">Snap<span>API</span> Blog</div>
<h1>{title}</h1>
<div class="meta">By <span>{author}</span> · {date}</div>
</body>
</html>
"""
response = requests.post(
"https://api.snapapi.pics/v1/screenshot",
headers={
"X-Api-Key": SNAPAPI_KEY,
"Content-Type": "application/json",
},
json={
"html": html,
"format": "png",
"width": 1200,
"height": 630,
"responseType": "image",
},
timeout=30,
)
response.raise_for_status()
return response.content
# Generate for a blog post
image = generate_og_image(
"Building AI Agents with Web Data",
"SnapAPI Team",
"February 9, 2026"
)
with open("og-image.png", "wb") as f:
f.write(image)
print(f"Generated OG image: {len(image)} bytes")
Serving OG Images Dynamically
In a real application, you want to serve OG images on demand. Here is an Express.js endpoint that generates and caches OG images:
const express = require("express");
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
const app = express();
const CACHE_DIR = "./og-cache";
const SNAPAPI_KEY = "your_api_key";
// Ensure cache directory exists
fs.mkdirSync(CACHE_DIR, { recursive: true });
app.get("/og/:slug.png", async (req, res) => {
const { slug } = req.params;
// Check cache first
const cacheKey = crypto.createHash("md5").update(slug).digest("hex");
const cachePath = path.join(CACHE_DIR, `${cacheKey}.png`);
if (fs.existsSync(cachePath)) {
res.setHeader("Content-Type", "image/png");
res.setHeader("Cache-Control", "public, max-age=86400");
return res.sendFile(path.resolve(cachePath));
}
// Look up post data from your database
const post = await getPostBySlug(slug);
if (!post) return res.status(404).send("Not found");
// Generate OG image
const html = template(post.title, post.author, post.date);
const response = await fetch("https://api.snapapi.pics/v1/screenshot", {
method: "POST",
headers: {
"X-Api-Key": SNAPAPI_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
html,
format: "png",
width: 1200,
height: 630,
responseType: "image",
}),
});
if (!response.ok) {
return res.status(500).send("Failed to generate image");
}
const buffer = Buffer.from(await response.arrayBuffer());
// Write to cache
fs.writeFileSync(cachePath, buffer);
// Serve the image
res.setHeader("Content-Type", "image/png");
res.setHeader("Cache-Control", "public, max-age=86400");
res.send(buffer);
});
app.listen(3000, () => console.log("OG image server on :3000"));
Then reference it in your HTML:
<meta property="og:image" content="https://yoursite.com/og/my-blog-post.png">
Caching Strategies
Generating OG images on every request is wasteful. Here are three proven caching approaches:
1. Build-Time Generation
Generate images during your build step (e.g., in a Next.js getStaticProps or a custom build script). Best for static sites where content changes infrequently.
2. On-Demand + File Cache
Generate on first request, save to disk, and serve from cache on subsequent requests (as shown in the Express example above). Good for sites with moderate traffic.
3. CDN Edge Caching
Put your OG image endpoint behind a CDN like Cloudflare or AWS CloudFront. Set long Cache-Control headers so the CDN caches the image at the edge. Best for high-traffic sites.
// Set headers for CDN caching
res.setHeader("Cache-Control", "public, max-age=604800, s-maxage=2592000");
res.setHeader("CDN-Cache-Control", "max-age=2592000");
Pro tip: Use AVIF format instead of PNG for 50% smaller files. Just change format: "png" to format: "avif". Most social platforms now support AVIF, and the smaller file size means faster page loads when platforms fetch your OG image.
Template Ideas
Here are some common OG image template patterns you can build:
- Blog post card: Title + author + publication date + category tag
- Product page: Product name + price + hero image (use an
<img>tag with an absolute URL) - User profile: Avatar + username + bio + stats (followers, posts)
- Documentation: Page title + section breadcrumb + brand logo
- Changelog: Version number + release date + key feature highlights
Since you are writing full HTML/CSS, the design possibilities are unlimited. You can use Google Fonts (via <link> tag), SVG icons, background images, and any CSS feature supported by Chromium.
Testing Your OG Images
After implementing, test your OG images with these tools:
- Facebook Sharing Debugger -- validates OG tags and shows preview
- Twitter Card Validator -- previews how your link appears on Twitter/X
- LinkedIn Post Inspector -- tests LinkedIn link previews
Next Steps
- API Documentation -- full reference for the
htmlparameter and all options - Screenshot API with Python -- more Python examples
- Screenshot API with Node.js -- Node.js integration guide
Related Reading
- Social Media Cards Use Case โ automate OG image generation at scale
- Free Screenshot API โ get started with 200 free captures per month
- Extract API for LLMs โ extract page content for dynamic card generation