Generate Open Graph Images from HTML with a Free API

2026-04-30 | Tags: [html2image, og-images, social-media, api, automation]

When someone shares your link on Twitter, LinkedIn, or Slack, the preview image is your Open Graph image. A good one gets clicks. A missing one gets ignored.

Most OG image generators use templates with limited customization. With an HTML-to-image API, your OG image is whatever you can build with HTML and CSS.

Quick Start

curl -X POST "https://hermesforge.dev/api/html2image" \
  -H "Content-Type: application/json" \
  -d '{"html": "<div style=\"width:1200px;height:630px;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;font-family:system-ui;color:white;font-size:48px;padding:60px;text-align:center\">How to Build APIs That People Actually Use</div>", "width": 1200, "height": 630, "format": "png"}' \
  -o og-image.png

That's a 1200x630 OG image with a gradient background and centered title. No Puppeteer, no canvas, no server-side rendering setup.

Python: Dynamic OG Images for Blog Posts

import requests
import json

def generate_og_image(title, author="", date="", output="og.png"):
    """Generate an OG image for a blog post."""
    html = f"""
    <div style="
        width: 1200px; height: 630px;
        background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
        display: flex; flex-direction: column;
        justify-content: center; padding: 80px;
        font-family: system-ui, -apple-system, sans-serif;
        color: white;
    ">
        <h1 style="font-size: 52px; margin: 0 0 20px 0; line-height: 1.2;">{title}</h1>
        <div style="font-size: 24px; color: #a0a0ff;">
            {f'{author} · ' if author else ''}{date}
        </div>
    </div>
    """

    response = requests.post(
        "https://hermesforge.dev/api/html2image",
        json={"html": html, "width": 1200, "height": 630, "format": "png"},
    )

    with open(output, "wb") as f:
        f.write(response.content)

    print(f"Generated {output} ({len(response.content)} bytes)")

# Generate OG images for your blog posts
posts = [
    {"title": "Building a REST API in 30 Minutes", "date": "March 2026"},
    {"title": "Why Your CI/CD Pipeline Is Slower Than It Should Be", "date": "April 2026"},
    {"title": "The Case for Boring Technology", "date": "May 2026"},
]

for i, post in enumerate(posts):
    generate_og_image(
        title=post["title"],
        author="Your Name",
        date=post["date"],
        output=f"og-{i+1}.png",
    )

Social Card Templates

Twitter Card (Summary Large Image)

html = """
<div style="
    width: 1200px; height: 628px;
    background: #1a1a2e; color: white;
    font-family: system-ui; display: flex;
">
    <div style="flex: 1; padding: 60px; display: flex; flex-direction: column; justify-content: center;">
        <h1 style="font-size: 44px; margin: 0 0 16px 0;">Your Article Title</h1>
        <p style="font-size: 22px; color: #888; margin: 0;">A brief description that makes people want to click</p>
    </div>
    <div style="width: 300px; background: linear-gradient(180deg, #e94560, #c81e45); display: flex; align-items: center; justify-content: center;">
        <span style="font-size: 120px;">🚀</span>
    </div>
</div>
"""

LinkedIn Post Image

html = """
<div style="
    width: 1200px; height: 627px;
    background: white; color: #333;
    font-family: system-ui; padding: 80px;
    display: flex; flex-direction: column;
    justify-content: space-between;
">
    <div>
        <h1 style="font-size: 48px; margin: 0 0 20px 0; color: #0a66c2;">
            5 Lessons from Scaling to 1M Requests
        </h1>
        <p style="font-size: 24px; color: #666; line-height: 1.5;">
            What we learned about caching, rate limiting, and graceful degradation.
        </p>
    </div>
    <div style="font-size: 20px; color: #999; border-top: 2px solid #eee; padding-top: 20px;">
        yoursite.com · 5 min read
    </div>
</div>
"""

Retina Quality

Generate 2x resolution images for crisp display on high-DPI screens:

curl -X POST "https://hermesforge.dev/api/html2image" \
  -H "Content-Type: application/json" \
  -d '{"html": "<div style=\"width:600px;height:315px;background:#1a1a2e;color:white;display:flex;align-items:center;justify-content:center;font-size:32px;font-family:system-ui\">Retina Sharp</div>", "width": 600, "height": 315, "scale": 2, "format": "webp", "quality": 90}' \
  -o og-retina.webp

The output is 1200x630 pixels at 2x density — perfect for retina displays, served as WebP for smaller file sizes.

Batch Generation for Static Sites

Generate OG images for every page in a static site at build time:

import requests
import json
import os

POSTS = [
    {"slug": "getting-started", "title": "Getting Started Guide"},
    {"slug": "api-reference", "title": "API Reference"},
    {"slug": "deployment", "title": "Deployment Guide"},
    {"slug": "changelog", "title": "Changelog"},
]

OUTPUT_DIR = "public/og"
os.makedirs(OUTPUT_DIR, exist_ok=True)

TEMPLATE = """
<div style="
    width:1200px; height:630px;
    background: linear-gradient(135deg, {bg1}, {bg2});
    display:flex; align-items:center; justify-content:center;
    font-family: system-ui; color: white; padding: 80px;
    text-align: center;
">
    <h1 style="font-size: 56px; margin: 0;">{title}</h1>
</div>
"""

GRADIENTS = [
    ("#667eea", "#764ba2"),
    ("#f093fb", "#f5576c"),
    ("#4facfe", "#00f2fe"),
    ("#43e97b", "#38f9d7"),
]

for i, post in enumerate(POSTS):
    gradient = GRADIENTS[i % len(GRADIENTS)]
    html = TEMPLATE.format(title=post["title"], bg1=gradient[0], bg2=gradient[1])

    resp = requests.post(
        "https://hermesforge.dev/api/html2image",
        json={"html": html, "width": 1200, "height": 630, "format": "webp", "quality": 85},
    )

    path = os.path.join(OUTPUT_DIR, f"{post['slug']}.webp")
    with open(path, "wb") as f:
        f.write(resp.content)

    print(f"Generated {path}")

Add to your HTML head:

<meta property="og:image" content="https://yoursite.com/og/getting-started.webp" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />

Dynamic OG Images with a Serverless Function

Generate OG images on-demand by proxying through a serverless function:

// Vercel/Netlify serverless function
export default async function handler(req) {
  const title = req.query.title || 'My Site';
  const html = `
    <div style="width:1200px;height:630px;background:#1a1a2e;color:white;
         display:flex;align-items:center;justify-content:center;
         font-family:system-ui;font-size:48px;padding:80px;text-align:center">
      ${title}
    </div>
  `;

  const response = await fetch('https://hermesforge.dev/api/html2image', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ html, width: 1200, height: 630, format: 'png' }),
  });

  return new Response(await response.arrayBuffer(), {
    headers: { 'Content-Type': 'image/png', 'Cache-Control': 'public, max-age=86400' },
  });
}

Then use it as your OG image URL:

<meta property="og:image" content="https://yoursite.com/api/og?title=My+Article+Title" />

Email Headers

Generate branded email header images:

html = """
<div style="
    width: 600px; height: 200px;
    background: linear-gradient(90deg, #2c3e50, #3498db);
    display: flex; align-items: center; padding: 0 40px;
    font-family: system-ui; color: white;
">
    <div>
        <h1 style="margin: 0; font-size: 28px;">Weekly Newsletter</h1>
        <p style="margin: 8px 0 0; font-size: 16px; color: #bdc3c7;">Issue #42 · March 2026</p>
    </div>
</div>
"""

resp = requests.post(
    "https://hermesforge.dev/api/html2image",
    json={"html": html, "width": 600, "height": 200, "format": "png"},
)

with open("email-header.png", "wb") as f:
    f.write(resp.content)

API Parameters

Parameter Type Default Description
html string required HTML content to render
width int 800 Viewport width (max 1920)
height int 600 Viewport height (max 1080)
format string png Output: png, jpeg, webp
scale int 1 Pixel ratio: 1, 2, or 3
background string white CSS background color
quality int 80 JPEG/WebP quality (1-100)

Rate Limits

Tier Limit Cost
Anonymous 5/min, 10/day Free
API Key 50/day Free
Pro 1000/day $9 / 30 days
Ultra 5000/day $29 / 30 days

Get a free API key:

curl -X POST "https://hermesforge.dev/api/keys" -H "Content-Type: application/json" -d '{}'