HTML to Image: Meaning and the Three Ways to Do It

HTML to image means rendering markup and CSS into a PNG, JPEG or WebP file. Compare the three approaches and see tested Puppeteer and Playwright code.

By Simon Chiu · Updated

HTML to image means rendering a piece of HTML and CSS, usually a template filled with data, into a raster image file such as PNG, JPEG or WebP, so the result can be used where HTML cannot: social cards, emails, chat previews, certificates and documents.

The idea is simple: design with the tools you already know, and get a picture out. The differences lie in what does the rendering. This entry is part of the screenshot and rendering glossary.

A plain explanation

Many places on the internet accept only images. An og:image must be a picture file. Email clients strip most CSS. A Slack preview shows a thumbnail. HTML to image lets you build that picture as a web page, with layout, fonts and data from your app, and then convert it.

template + data ──▶ HTML string ──▶ renderer ──▶ pixels ──▶ PNG / JPEG / WebP
 (escape input)      (full doc)     (browser,      (viewport    (encode, then
                                    DOM redraw     or element)   store or serve)
                                    or SVG)

Three approaches

Approach Examples CSS support Runs where Main limits
Headless browser screenshot Puppeteer, Playwright, screenshot APIs Everything Chrome supports Server or API Needs a browser process; slower to start
DOM redraw in the visitor's browser html2canvas, html-to-image, dom-to-image Partial; reimplemented or via SVG foreignObject Client side Cross-origin images taint the canvas; fonts must be inlined
HTML/JSX to SVG converter Satori Flexbox subset, no grid, limited selectors Server or edge, no browser You write to its subset, not to the web platform

Pick by where the input comes from and how faithful the output must be. For a template you control and need fast at the edge, a converter can be enough. To capture what a real browser shows, including JavaScript output, web fonts and modern CSS, you need a browser engine.

A card rendered from a template

A 1200 by 630 dark navy card with the white headline Release notes <v2.4> & what changed, the angle brackets and ampersand shown as text, and a grey line renderscreenshot.com/changelog below it.
A 1200 x 630 card rendered from an HTML string with Puppeteer on 2026-10-15. The title contains <, > and &; escaping makes them print as text instead of breaking the markup.

How it works technically, in a browser engine

  1. Load the HTML. From a string with setContent, or from a URL. Strings have no address, so relative asset paths need a base URL.
  2. Wait. For load, for web fonts (document.fonts.ready), and for any script that draws into the page.
  3. Choose the area. The viewport is the canvas, so set it to the image size. Or capture one element and let its box decide the size.
  4. Encode. PNG is lossless and supports transparency. JPEG and WebP are smaller for photos and gradients.
  5. Escape input. Any user text placed into the HTML must be escaped. Unescaped input can break the layout or run script inside your renderer.

The setting in Puppeteer, Playwright and RenderScreenshot

Puppeteer: a full-size card from a template. The viewport equals the card size.

import puppeteer from 'puppeteer';

// Escape anything that comes from users before it goes into markup.
const esc = (s) => String(s).replace(/[&<>"']/g, (c) =>
  ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);

const title = 'Release notes <v2.4> & what changed';
const html = `<!doctype html><html><head><meta charset="utf-8"><style>
  body{margin:0;width:1200px;height:630px;display:flex;flex-direction:column;justify-content:center;
       padding:0 80px;box-sizing:border-box;background:#0f172a;color:#f8fafc;font-family:Helvetica,Arial,sans-serif}
  h1{font-size:64px;line-height:1.1;margin:0 0 24px}
  p{font-size:28px;color:#94a3b8;margin:0}
</style></head><body><h1>${esc(title)}</h1><p>renderscreenshot.com/changelog</p></body></html>`;

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1200, height: 630 });
  await page.setContent(html, { waitUntil: 'load' });
  await page.evaluate(() => document.fonts.ready);
  await page.screenshot({ path: 'out/h2i-card.png' });
} finally {
  await browser.close();
}

Playwright: an element at 2x with a transparent background. The output is the element's size, here a 330 x 86 RGBA PNG from a 165 x 43 badge.

import { chromium } from 'playwright';

// Element capture: the image is exactly the size of the element, no viewport math.
const browser = await chromium.launch();
try {
  const page = await browser.newPage({ deviceScaleFactor: 2 });
  await page.setContent(`<!doctype html><meta charset="utf-8">
    <div id="badge" style="display:inline-block;padding:10px 18px;border-radius:999px;
      background:#16a34a;color:#fff;font:600 20px Helvetica,Arial,sans-serif">build passing</div>`);
  await page.locator('#badge').screenshot({ path: 'out/h2i-badge.png', omitBackground: true });
} finally {
  await browser.close();
}

RenderScreenshot: send the HTML instead of a URL. In the Node SDK, TakeOptions.html() replaces TakeOptions.url(); everything else, such as size, scale and format, works the same.

import { writeFile } from 'node:fs/promises';
import { Client, TakeOptions } from 'renderscreenshot';

const client = new Client(process.env.RS_API_KEY);
const image = await client.take(
  TakeOptions.html(html).width(1200).height(630).format('png') // html: the escaped template above
);
await writeFile('card.png', image);

For POST bodies, the fields are html, html_base_url, viewport and output. output.transparent gives a transparent PNG or WebP. The HTML to image guide covers fonts, payload size and escaping in detail, and the HTML to image API page covers the use cases.

Tested on 2026-10-15 with Node 22.18, Puppeteer 24.43.1 (headless shell, Chrome 148) and Playwright 1.64.1 (Chromium 156). The RenderScreenshot snippet follows the SDK docs and was not run for this page.

Common mistakes

  • Leaving the viewport at the default. The card is drawn in a 1280 x 800 window and cropped wrong. Set the viewport to the image size, or capture an element.
  • No escaping. A title with < or & breaks the markup, and user input can inject script.
  • Capturing before fonts load. The first render often uses the fallback font. Wait for fonts, as in the Puppeteer listing.
  • Choosing a DOM-redraw library for third-party content. Cross-origin images and unsupported CSS fail silently in the visitor's browser.
  • HTML base URL: why relative image paths break in rendered strings.
  • OG image: the most common output of HTML to image.
  • Satori: the converter approach in detail.

To render your own templates without running a browser, get a free API key.

Frequently asked questions

Is html-to-image the same as the npm package of that name?
The term means any conversion of HTML and CSS into a raster image. html-to-image is also the name of a browser-side npm library that redraws DOM nodes through SVG foreignObject. It is one of the three approaches on this page.
Which approach gives the most accurate image?
A real browser engine, such as headless Chrome driven by Puppeteer, Playwright or a screenshot API. It runs the same layout, font and CSS code as the browser your visitors use. The other approaches reimplement or reuse parts of it and support less CSS.
How do I make a transparent PNG from HTML?
Give the page no background and capture with a transparency option: omitBackground in Puppeteer and Playwright, or output.transparent in RenderScreenshot, which works for PNG and WebP.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.