HTML to image: convert HTML to PNG or JPEG with an API
Convert HTML to PNG, JPEG or PDF with one API call. Render your own markup for Open Graph images, invoices and certificates, fonts and CSS intact.
Converting HTML to an image sounds like it should be simple, and it is, as long as something renders the HTML properly first. Libraries that rasterise markup without a browser engine get fonts, flexbox and grid subtly wrong. The reliable way to convert HTML to PNG or JPEG is to render it in a real browser and screenshot the result.
That is what an HTML to image API does. You post markup, it renders in headless Chrome, and you get an image back. Every CSS feature your browser supports works, because it is the same engine.
This guide covers the HTML path. To capture a live page from its address instead, see the website screenshot API guide.
Send HTML instead of a URL
Swap the url field for html and post your markup directly. Nothing needs to be hosted anywhere.
curl -X POST "https://screenshotbee.com/api/capture-screenshot" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"html": "<html><body><h1>Hello, world!</h1></body></html>",
"format": "png",
"viewport_width": 1200,
"viewport_height": 630
}'Sizing the output
For HTML to image work you almost always want an exact output size rather than a full page capture. Set the viewport to the dimensions you need and let your markup fill it.
The sizes worth memorising: 1200 by 630 for Open Graph and Twitter cards, 1080 by 1080 for a square social post, 1080 by 1920 for a story. Set device_scale_factor to 2 if you want a retina asset.
{
"html": "<html>...</html>",
"format": "png",
"viewport_width": 1200,
"viewport_height": 630,
"device_scale_factor": 2
}Open Graph images, generated per page
The most common reason people convert HTML to an image is Open Graph cards. You want every blog post, product page or user profile to have its own share image, and you do not want to open a design tool a thousand times.
Build one HTML template, interpolate the title, author and any artwork, and post it. One template becomes thousands of unique images, and updating the design means editing the template rather than regenerating a library of files by hand.
const template = (title, author) => `
<html>
<head>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@700" rel="stylesheet">
<style>
body { margin: 0; width: 1200px; height: 630px; display: flex;
flex-direction: column; justify-content: center;
padding: 64px; box-sizing: border-box;
font-family: Inter, sans-serif; background: #1A1A1A; color: #F7F4F2; }
h1 { font-size: 64px; line-height: 1.1; margin: 0 0 24px; }
p { font-size: 28px; color: #A19F9E; margin: 0; }
</style>
</head>
<body><h1>${title}</h1><p>${author}</p></body>
</html>`;
const res = await fetch("https://screenshotbee.com/api/capture-screenshot", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.SCREENSHOTBEE_API_KEY,
},
body: JSON.stringify({
html: template("How we cut render time in half", "Priya Raman"),
format: "png",
viewport_width: 1200,
viewport_height: 630,
}),
});
const { result } = await res.json();
// result.screenshotUrl is your finished cardWeb fonts, CSS and JavaScript all work
Because rendering happens in a real headless Chrome instance, you are not working against a limited subset. Flexbox, grid, custom properties, gradients, transforms, SVG and web fonts all behave the way they do in your own browser.
The one thing to watch is timing. A web font loaded from a CDN arrives over the network, and if the capture fires first you get a fallback typeface. Add a short wait, or inline the font as a data URI to remove the network round trip entirely.
{
"html": "<html>...</html>",
"viewport_width": 1200,
"viewport_height": 630,
"has_screenshot_wait_time": true,
"screenshot_wait_time": 1000
}What people build with it
- Open Graph and Twitter card images generated per page, so every share looks designed
- Invoices and receipts rendered from the same template that produces the on screen version
- Certificates and badges personalised per user, at signup or on completion
- Charts and reports turned into images for email, where JavaScript will not run
- Email previews rendered exactly as a browser would draw them
- Product mockups and price cards for marketplaces and comparison sites
PNG, JPEG or PDF
PNG is the right default: lossless, transparent where you need it, and the format every social platform accepts. JPEG is smaller for photographic content where the file size matters more than a crisp edge. PDF is the one to pick when the output is a document rather than a picture, such as an invoice a customer will download and print.
Set format to png, jpeg or pdf. For JPEG you can also tune image_quality to trade file size against artefacts.
Common problems
- Fallback fonts in the output: the web font had not loaded. Add a wait, or inline the font
- Unexpected white margin: the browser default body margin is still there. Set margin 0 explicitly
- Content cut off: your markup is taller than the viewport. Either raise viewport_height or switch to a full page capture
- Blurry text on retina displays: set device_scale_factor to 2
- External images missing: they were still downloading. Inline them as data URIs or add a wait
What it costs
One rendered image is one credit, whether it started as a URL or as raw HTML. Credits start at $9 per 1,000 and fall to $3.50 per 1,000 at volume, they never expire, and a render that fails is never charged. New accounts get 100 free credits with no card, which is enough to build and test a whole Open Graph pipeline before you pay anything. See the pricing page for the full ladder.
Ready to try ScreenshotBee?
Start with 100 free credits. No credit card, no subscription.
Get started free