Tutorial · June 7, 2026

HTML to Image API: When to Render HTML to PNG Instead of Screenshotting a URL

By the SnapshotFlow Team Published ~8 min read

A screenshot API can take two types of input: a URL it navigates to, or a raw HTML payload it renders directly. Most teams default to URLs — but for a whole class of tasks, passing HTML is faster, simpler, and cheaper. This guide explains when and why, with working code examples.

What Is an HTML to Image API?

An HTML to Image API is the same headless-browser pipeline as a URL screenshot API, except the input is HTML markup instead of a web address. You POST your template — with inline or CDN-loaded CSS and optionally a small script — and receive back a PNG, JPEG, WebP, or PDF. The page never needs to be hosted anywhere.

In SnapshotFlow, this is exposed via the html parameter on the existing /screenshot endpoint. When html is present, the API skips navigation entirely and renders the provided markup directly in the headless browser. All other parameters — width, height, full_page, selector, format, response_type — behave identically.

HTML Input vs URL Input: Which One to Use

Both modes use the same browser engine, so the output quality is identical. The difference is purely about where your content comes from and what friction you're willing to tolerate.

DimensionURL screenshotHTML input
Content lives at a public URL✅ Natural fitUnnecessary overhead
Content is dynamically generated per-requestRequires hosting a route first✅ Natural fit
Page is behind auth / not publicly reachableNeeds cookie/header injection✅ Just pass the markup
Pixel-perfect control over layoutLive page may shift with data changes✅ Template is frozen per-call
Content includes real user dataRequires a per-user page or auth flow✅ Interpolate at call time
Third-party site you don't control✅ Only optionN/A
Monitoring / regression testing✅ Captures live stateNot useful
Rule of thumb: if you control the content and generate it at request time, use HTML input. If you're capturing a page that already exists on the web, use a URL.

What Teams Actually Use HTML-to-Image For

Open Graph & social cards

Generate per-post OG images from an HTML template. No need to publish a /og/[slug] route — render the card on the fly and cache it. See the OG image generation guide.

Invoices & receipts

Render a data-filled HTML invoice template to PDF or PNG. Consistent layout guaranteed — no browser quirks from end-user machines.

Email previews

Capture what an email template looks like before sending. Pass the HTML directly, no mail client required.

Certificates & badges

Personalized completion certificates with a recipient's name, date, and course title — generated in one API call.

Report cover pages

Combine a branded HTML template with dynamic data (client name, report period, KPIs) and convert to PDF in the same request.

Design system snapshots

Render individual UI components from your design system in isolation for visual regression baselines without running a browser test suite.

Quick Start: Render HTML to PNG in One Call

The simplest possible request — POST a minimal HTML snippet and receive a PNG:

curl -X POST "https://api.snapshotflow.com/screenshot" \
  -H "X-Api-Key: $SNAPSHOTFLOW_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<!DOCTYPE html><html><body style=\"background:#0ea5e9;display:flex;align-items:center;justify-content:center;height:100vh;margin:0\"><h1 style=\"color:white;font-family:sans-serif\">Hello from HTML</h1></body></html>",
    "width": 1200,
    "height": 630,
    "format": "png"
  }' \
  --output hello.png

For a realistic OG card with an external font, use a full HTML document:

curl -X POST "https://api.snapshotflow.com/screenshot" \
  -H "X-Api-Key: $SNAPSHOTFLOW_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<!DOCTYPE html><html><head><link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css2?family=Inter:wght@700&display=swap\"><style>*{margin:0;box-sizing:border-box}body{width:1200px;height:630px;background:linear-gradient(135deg,#0f172a,#1e3a5f);display:flex;flex-direction:column;justify-content:center;padding:80px;font-family:Inter,sans-serif;color:white}.tag{font-size:14px;color:#38bdf8;letter-spacing:.1em;text-transform:uppercase;margin-bottom:20px}h1{font-size:64px;line-height:1.1;font-weight:700}.domain{margin-top:40px;font-size:20px;color:#64748b}</style></head><body><div class=\"tag\">Tutorial · June 2026</div><h1>HTML to Image API</h1><div class=\"domain\">snapshotflow.com</div></body></html>",
    "width": 1200,
    "height": 630,
    "format": "png",
    "response_type": "url"
  }' 

Setting response_type=url returns a CDN link instead of binary — useful when you want to store the URL in a database or pass it directly to a social platform.

Code Examples

import os, requests

# Build your template dynamically
html = f"""
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@700&display=swap">
  <style>
    body {{ margin:0; width:1200px; height:630px; background:#0f172a;
            display:flex; align-items:center; justify-content:center;
            font-family:Inter,sans-serif; color:white; }}
    h1   {{ font-size:72px; font-weight:700; }}
    p    {{ font-size:24px; color:#94a3b8; margin-top:16px; }}
  </style>
</head>
<body>
  <div>
    <h1>Invoice #{'{invoice_id}'}</h1>
    <p>{'{customer_name}'} · {'{amount}'}</p>
  </div>
</body>
</html>
""".format(invoice_id="INV-2026-0042", customer_name="Acme Corp", amount="$4,200.00")

resp = requests.post(
    "https://api.snapshotflow.com/screenshot",
    json={
        "html": html,
        "width": 1200,
        "height": 630,
        "format": "png",
    },
    headers={"X-Api-Key": os.environ["SNAPSHOTFLOW_KEY"]},
    timeout=30,
)
resp.raise_for_status()
with open("invoice-card.png", "wb") as f:
    f.write(resp.content)
const html = `
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@700&display=swap">
  <style>
    body { margin:0; width:1200px; height:630px; background:#0f172a;
           display:flex; align-items:center; justify-content:center;
           font-family:Inter,sans-serif; color:white; }
    h1 { font-size:72px; font-weight:700; }
    p  { font-size:24px; color:#94a3b8; margin-top:16px; }
  </style>
</head>
<body>
  <div>
    <h1>Invoice $\{invoiceId\}</h1>
    <p>$\{customerName\} · $\{amount\}</p>
  </div>
</body>
</html>
`;

const res = await fetch("https://api.snapshotflow.com/screenshot", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Api-Key": process.env.SNAPSHOTFLOW_KEY,
  },
  body: JSON.stringify({
    html,
    width: 1200,
    height: 630,
    format: "png",
    response_type: "url",   // get a CDN link back
  }),
});
const { url } = await res.json();
console.log("Image URL:", url);
<?php
$html = '<!DOCTYPE html><html><head>
  <style>
    body { margin:0; width:1200px; height:630px; background:#0f172a;
           display:flex; align-items:center; justify-content:center;
           font-family:sans-serif; color:white; }
    h1 { font-size:72px; font-weight:700; }
  </style>
</head>
<body><h1>Hello SnapshotFlow</h1></body></html>';

$ch = curl_init('https://api.snapshotflow.com/screenshot');
curl_setopt_array($ch, [
  CURLOPT_POST           => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER     => [
    'Content-Type: application/json',
    'X-Api-Key: ' . getenv('SNAPSHOTFLOW_KEY'),
  ],
  CURLOPT_POSTFIELDS => json_encode([
    'html'   => $html,
    'width'  => 1200,
    'height' => 630,
    'format' => 'png',
  ]),
]);
$image = curl_exec($ch);
file_put_contents('output.png', $image);

Rendering Tips for Pixel-Perfect Output

1. Set explicit body dimensions

The headless browser uses the width and height parameters as the viewport. Your HTML should match these dimensions exactly — set margin:0 on body and pin width/height with CSS to avoid unexpected overflow or empty space.

2. Use CDN-hosted fonts, not local ones

Google Fonts and Bunny Fonts load fine from the render worker. Include the <link> tag inside your HTML <head>. To guarantee font availability on the first render without a network round-trip, add delay=500 to let the font download complete, or base64-encode the font and inline it as a @font-face.

3. Inline critical CSS

For self-contained renders that don't depend on external stylesheets, embed all CSS in a <style> block. This makes the render deterministic and avoids latency from external stylesheet fetches.

4. Use selector to crop to a component

If you're rendering a full page but only want one element — a card, a chart, a badge — pass selector=".my-component". The browser renders the whole document but the returned image is cropped to that element's bounding box. No manual clipping needed.

5. Base64-encode for large or dynamic HTML

If your HTML contains characters that are awkward in JSON (quotes, emoji, special characters), encode the entire string as base64 before passing it. SnapshotFlow accepts both raw and base64-encoded values for the html parameter.

Caching note. HTML renders are cached by a hash of the full parameter set including the HTML content. If you pass dynamic data (customer name, invoice number) directly in the HTML string, each unique combination will produce a separate cache entry — which is usually what you want. If you want to share cache entries across users for template-like content, keep the HTML constant and inject data via scripts. Cache hits don't count against your quota — see how screenshot API pricing works.

HTML to PNG API

PNG is the default output format and the right choice whenever you need lossless quality or a transparent background — OG cards, social banners, certificates, email preview thumbnails. Set format=png (or omit it entirely) and the API returns binary PNG data.

For transparent output — badges, stickers, or UI components overlaid on other images — add omit_background=true. The browser's white default background is removed, leaving only your markup's actual pixels.

curl -X POST "https://api.snapshotflow.com/screenshot" \
  -H "X-Api-Key: $SNAPSHOTFLOW_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<div style=\"padding:24px;background:#0ea5e9;border-radius:12px;font-family:sans-serif;color:white\">Hello PNG</div>",
    "width": 400,
    "height": 80,
    "format": "png",
    "omit_background": true
  }' --output badge.png

For Retina / 2× output, add device_scale_factor=2 — the viewport stays at logical pixels but the output image is doubled in resolution. Useful for OG cards displayed on high-DPI screens.

HTML to PDF API

Switch to format=pdf and the same HTML input produces a PDF instead of a raster image. This is the fastest way to generate invoices, contracts, and reports from a template: no headless browser to manage locally, no wkhtmltopdf dependency, no puppeteer PDF quirks.

Key parameters for PDF output:

ParameterValueEffect
formatpdfSwitch output from image to PDF
pdf_formatA4, A3, Letter, Legal, TabloidPage size
full_pagetrueAllow multi-page PDF based on content height
pdf_print_backgroundtrueInclude CSS background colours and images
landscapetrueRotate page to landscape orientation
curl -X POST "https://api.snapshotflow.com/screenshot" \
  -H "X-Api-Key: $SNAPSHOTFLOW_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<!DOCTYPE html><html><head><style>body{font-family:sans-serif;padding:40px}h1{color:#0f172a}</style></head><body><h1>Invoice INV-2026-0042</h1><p>Acme Corp · $4,200.00</p></body></html>",
    "format": "pdf",
    "pdf_format": "A4",
    "full_page": true,
    "pdf_print_background": true
  }' --output invoice.pdf

For multi-page documents, structure your HTML with natural page breaks using CSS page-break-before: always or break-before: page. The browser's print engine handles pagination identically to window.print().

Key Parameters for HTML Renders

These are the parameters you'll use most often when rendering HTML. The full list is in the API docs.

ParameterDefaultNotes for HTML renders
html""Raw HTML string or base64-encoded HTML. Takes priority over url when both are present.
width1280Set this to match your template's intended width (e.g. 1200 for OG cards).
height800Set to match template height. For variable-length content, use full_page=true instead.
full_pagefalseUseful for long HTML documents like invoices where height varies by content.
selector""Crop output to a specific DOM element. Ideal for component-level captures.
formatpngpng, jpeg, webp, or pdf. PDF is popular for invoices and reports.
omit_backgroundfalseTransparent PNG output. Useful for badges and stickers placed over other images.
delay0Add 300–800 ms when loading external fonts to avoid FOUT in the capture.
scripts""Inject JavaScript to populate dynamic data after the HTML is loaded.
response_typeimageimage (binary), json, base64, or url (CDN link).
cachetrueCache keyed on full parameter hash — safe for dynamic HTML since content is part of the key.

FAQ

What is the best HTML to PNG API in 2026?

The best HTML to PNG API depends on your use case. If you need a hosted service with a free tier, caching, and async delivery, SnapshotFlow offers 200 free screenshots per month with no credit card. If you have data-residency requirements, its self-hosted Docker option keeps everything inside your network. For a broader comparison, see Best Screenshot APIs in 2026.

How do I convert a URL to an image with an API?

Pass url=https://example.com instead of the html parameter. The API navigates to the URL, renders the page in a real headless browser, and returns a PNG, JPEG, WebP, or PDF. Use full_page=true to capture the full scrollable height. The screenshot API guide covers URL-mode in detail.

How do I use an HTML to Image API for OG image generation?

POST an HTML template with your article title, date, and branding as inline styles. Set width=1200 and height=630 (the standard OG card size), format=png, and response_type=url to get back a CDN link you can set as og:image. The OG image generation guide has a complete template and caching strategy.

What's the difference between an HTML to PDF API and a URL to PDF API?

The rendering engine is the same — Chromium's print pipeline — but the input differs. HTML to PDF is ideal for server-generated documents (invoices, contracts) where the content doesn't exist at a public URL. URL to PDF is better for capturing existing pages (reports, articles). In SnapshotFlow, you switch between the two by passing either html or url, then setting format=pdf.

Can I render an HTML string to image without hosting it?

Yes — that's the main purpose of the html parameter. POST your markup and receive the image in the response. No server, no public URL, no deployment. Works for OG cards, invoices, certificates, email previews, and any other dynamically generated content.

Can I load external fonts and CSS in an HTML render?

Yes. The headless browser makes real network requests, so Google Fonts, Bunny Fonts, and CDN-hosted stylesheets load normally. Add delay=500 to ensure fonts finish downloading before capture. For deterministic renders without network dependence, inline your CSS in a <style> block or base64-encode fonts as @font-face.

Start Rendering HTML to Images

200 free screenshots for the lifetime of the account. No credit card. MCP-ready, self-host available.