ScreenshotRender
← Back to blog
Tutorials

How to Convert HTML to PNG: 3 Methods That Work

Robert Belt·9 min read
Updated On :
Illustration of HTML code blocks beside a developer, converting markup into a PNG image

Short answer: to turn HTML into a PNG, you render the markup in a real browser and capture the result, either in the page with html2canvas, on a server with Puppeteer, or through a screenshot API. By the end of this you'll know which of those three fits a one-off, a server job, and a production pipeline, plus how to stop the image drifting from the HTML you fed it.

What's the fastest way to convert HTML to a PNG?

The fastest way to convert HTML to a PNG in code is a screenshot API: put your HTML at a URL, send that URL to one endpoint, and read a hosted image out of the JSON. With ScreenshotRender the whole call is one line: https://screenshotrender.com/api/v1/screenshot?apiKey=YOUR_API_KEY&url=https://github.com&fullPage=true. Swap the url for your own template route, like an /og page that renders the card you want, and the response carries the image URL at data.screenshot.

The three methods run from least to most automatable, and each fits a different situation.

  • html2canvas. A raw HTML string you only need once, rendered in the visitor's browser tab with no server at all.
  • Puppeteer. Full control on a server you own, where page.setContent(html) renders the string in real Chromium.
  • Screenshot API. Conversion at scale with nothing to maintain, once the HTML lives at a URL.

The rest of this post walks all three so you can pick by how much infrastructure you want to own.

Why doesn't html2canvas match what the browser shows?

html2canvas doesn't always match the browser because it doesn't use the browser's renderer. It reads the DOM and repaints it onto a <canvas> with its own partial reimplementation of CSS, then hands you a data URL through toDataURL. The call itself is short: html2canvas(el).then(c => c.toDataURL("image/png")), documented in the html2canvas docs.

It runs entirely in the visitor's tab, which is its whole appeal and also its ceiling. Because it re-implements rendering instead of using Chromium's, the output drifts from the real page in a few known ways.

  • Unsupported CSS. Newer properties like some filters, blend modes, and certain transforms render wrong or not at all, since the library has to support each one by hand.
  • Cross-origin images. An image from another domain taints the canvas unless it is served with CORS headers, and a tainted canvas makes toDataURL throw.
  • Browser only. It needs a live DOM in a tab, so it can't run on a server or against HTML you haven't mounted on a page.

For a quick client-side export, a chart or a receipt the user is already looking at, html2canvas is the least work. The moment you need it on a server or pixel-accurate, you want a real browser.

How do you convert HTML to an image with Puppeteer?

You convert HTML to an image with Puppeteer by loading the string with page.setContent, then calling page.screenshot, which renders the markup in real headless Chromium. The two lines that matter are await page.setContent(html, { waitUntil: "networkidle0" }) and await page.screenshot({ path: "out.png" }).

The networkidle0 wait matters because a raw string often pulls in fonts, images, or CSS that land after the initial parse, and without it you capture a half-painted page. The full launch-open-capture flow is in our Puppeteer screenshot guide, and because this is a genuine browser, the output matches what Chrome would draw, CSS and all.

The cost is everything around those two lines. You ship a Chromium binary, keep it patched, give each concurrent render enough memory, install the fonts your HTML expects, and absorb the version drift that turns a green CI run red after an unrelated upgrade. For a handful of renders on your laptop that is fine. At volume it becomes an infrastructure project of its own, which is where a lot of teams decide they would rather not own the server.

Skip the Chromium build. Just get the PNG.

You don't need to run headless Chromium to render HTML to an image. Host the HTML at a URL, send that URL to ScreenshotRender, and get back a hosted PNG from a real browser. 100 free renders a month, no credit card, no browser to patch or scale.

Try a render

How do you turn HTML into a PNG with a screenshot API?

You turn HTML into a PNG with a screenshot API by serving the HTML at a URL and pointing the API at that URL, which renders the page and returns a hosted image. The full request is one copy-pasteable line: https://screenshotrender.com/api/v1/screenshot?apiKey=YOUR_API_KEY&url=https://github.com&fullPage=true. The response is JSON, and the image lives at data.screenshot alongside the page title, description, and favicon.

The parameters are the whole surface. url is the page to render, fullPage=true captures the entire document instead of the default 1280 by 720 viewport, wait adds a delay for a template that finishes painting after load, and timeout caps how long a slow page can take. There is no SDK, so the same call works from Python, Node.js, and cURL because anything that speaks HTTP works identically.

This is exactly the pattern behind an Open Graph image: you build the HTML as a route on your own site, say /og?title=..., and the API screenshots it like any other page. If your input is already a URL rather than a raw HTML string, the sister walk-through is turning a webpage into an image. Either way, repeat captures of the same URL and options are served from an edge cache, so a dashboard that re-renders the same card doesn't pay for it twice. Once the template lives at a URL, generating ten thousand cards is the same one line as generating one.

When does HTML to image conversion fail?

HTML to image conversion fails in a few predictable ways, and almost all of them are about what the renderer can or can't load at capture time.

  • Missing fonts. A web font that hasn't loaded falls back silently and shifts the layout, so wait for fonts or inline the @font-face rule with the font embedded.
  • Relative or cross-origin assets. A raw HTML string with relative <img> or stylesheet paths has no base URL, so the images and CSS 404. Use absolute URLs for anything the HTML references.
  • Async rendering. Content that paints after load, like a JavaScript-driven chart or a lazy image, is missing if you capture too early. Add a wait so the page settles first.
  • Very tall output. A full-page capture of a long document can run to thousands of pixels and several megabytes, since the median page already weighs over 2 MB before you stretch it. Capture the viewport instead when you only need a card.

Match the fix to the symptom and the PNG comes back identical to the HTML you started with.

Common questions about converting HTML to PNG

How do I convert HTML to PNG in Python?

The quickest route in Python is one HTTP request to a screenshot API: host the HTML at a URL and GET the endpoint with the requests library, then read the image URL from data.screenshot. The full Python walk-through is in our Python screenshot guide. For a raw HTML string with no server, drive headless Chrome with Selenium or Playwright for Python and screenshot after setting the page content.

Can I get a JPG instead of a PNG?

Yes. PNG is the better default for HTML to image because it keeps text and flat color crisp, while JPG is smaller for photo-heavy output. A screenshot API returns a hosted image you can convert on your side with sharp in Node.js or Pillow in Python, so you choose the format at save time rather than at capture time.

How do I convert an HTML string instead of a URL?

Two options. Render the string in a browser you control: page.setContent in Puppeteer or html2canvas in the tab both take raw HTML. Or, to use a URL-based screenshot API, serve the string at a route first, even a temporary one, and capture that URL. A URL-based API needs an address to fetch, so the HTML has to live somewhere reachable.

Is there a free HTML to PNG converter?

Yes, for one-offs: paste-and-download web converters and browser DevTools both work with no code. For doing it in code, ScreenshotRender's free plan covers 100 renders a month with no credit card, which is enough to wire HTML to image into an app before you pay for volume.

Why are fonts or images missing in the output?

Usually the renderer captured before the assets loaded, or it couldn't reach them. A web font that hasn't finished loading falls back silently and shifts the layout, and a relative image path with no base URL returns a 404. Add a short wait so fonts and images settle before the capture, and use absolute URLs for anything the HTML references.

The honest takeaway: render HTML the same way a browser does, then capture it. For a one-off in the user's tab, html2canvas is the least work and you live with its CSS gaps. To render a dynamic string on a server you own, Puppeteer's setContent gives you every knob. And to turn a template into a PNG at scale from a single line, host the HTML at a URL and let a screenshot API do the rendering.

Keep reading