Images
How to Convert HTML to JPG or PNG (Browser Tool, Puppeteer and html2canvas Compared)
November 9, 2026 · 8 min read
To convert HTML to JPG or PNG you have three realistic options, and they fail in different ways. The quickest is a browser-based HTML to JPG and PNG converter: paste the markup, set a pixel size, download. The second is html2canvas, a JavaScript library that redraws part of a page you already have onto a canvas. The third is Puppeteer, which drives headless Chrome from Node and screenshots whatever it renders.
Which one you want comes down to two questions: is it a self-contained snippet (a social card, a quote graphic, a certificate) or a live page that fetches data and runs scripts, and does it need to happen once, by hand, or hundreds of times in a pipeline? Below: a short code sample for each method, the four things that break most often (web fonts, cross-origin images, foreignObject quirks, lazy loading), and which format and pixel size to export for cards and Open Graph images.
Three methods at a glance
The browser tool and Puppeteer render with a real browser engine, so modern CSS (flexbox, grid, gradients, custom properties) comes out the way the browser draws it; html2canvas re-implements CSS on a canvas and documents the properties it cannot draw.
| Method | Where it runs | Captures a live URL? | Install needed | Best for |
|---|---|---|---|---|
| Browser tool (paste HTML) | Your browser tab | No, you paste markup | None | One-off cards, OG images, inline-CSS templates |
| html2canvas | Inside your own web page | Only the page it is loaded in | npm package | A download-as-image button for a chart or share card |
| Puppeteer | Node on a server or in CI | Yes, any URL or HTML string | Node plus a Chrome download | Batch generation, live pages, JavaScript-built layouts |
Method 1: paste HTML into a browser tool
The free HTML to Image converter on this site is the no-install route from HTML code to a PNG, JPEG or WEBP file. It has three input tabs: HTML Code (pre-filled with a sample card; Reset restores it), Upload HTML (pick a .html or .htm file; its text lands in the editor and the tab shows a Loaded note with the file name) and Custom CSS (rules applied to both the preview and the export). Everything, including reading the uploaded file, happens in your browser tab; nothing is sent to a server.
Under Advanced Image Options, open by default, you choose the format (PNG, JPEG or WEBP; PNG selected), a scale of 1x, 2x or 3x (2x selected), a quality from 10 to 100 for JPEG and WEBP (90 by default; greyed out for lossless PNG), a background of White, Transparent or a custom colour, and a width and height in pixels, 800 by 400 to start. Four presets fill the size: Twitter Card 1200 by 628, OG Image 1200 by 630, Square 800 by 800 and Story 1080 by 1920. The button reads Generate & Download plus the format name.
Your markup goes into a foreignObject inside an SVG sized at width times scale by height times scale, with a viewBox of the plain width and height, so the layout is composed at your CSS size and rasterised at the multiplied size: 2x is real resolution, not upscaling. The SVG becomes a data URL, is loaded into an image element, painted onto a canvas and read back with toDataURL. No JavaScript in your HTML runs: not in the export, nor in the preview, a sandboxed iframe without script permission. The preview can otherwise fetch remote images and fonts; the export cannot, so trust the preview for layout, not for assets.
Method 2: html2canvas, when the HTML is already on your page
html2canvas is for the download-this-card button inside an app you control: it walks the DOM of an element that is already rendered, reads the computed styles and redraws them with the canvas API, so it captures only what is in the current document and opens no URL.
The call takes the element and an options object: scale multiplies the output resolution (default: the device pixel ratio), useCORS requests cross-origin images with CORS headers, and backgroundColor null keeps transparency instead of the default white fill. Coverage is the limit, because it redraws CSS itself: the documentation lists unsupported properties including box-shadow, filter and mix-blend-mode, and it does not render iframes or plugin content; if your card leans on those, use another method.
- import html2canvas from 'html2canvas';
- const canvas = await html2canvas(document.querySelector('#card'), { scale: 2, useCORS: true, backgroundColor: null });
- const link = document.createElement('a');
- link.download = 'card.png';
- link.href = canvas.toDataURL('image/png'); // or canvas.toDataURL('image/jpeg', 0.9)
- link.click();
Method 3: Puppeteer, when you need a real browser on a server
Puppeteer launches headless Chrome from Node, so it renders exactly what Chrome renders: external fonts and remote images load, scripts run, and you can point it at a URL or hand it an HTML string. It is the tool for a hundred OG images from one template, or for a page that assembles itself with JavaScript.
In the sample, deviceScaleFactor is the browser tool's Scale: the layout stays 1200 by 630 and the file is 2400 by 1260. page.screenshot writes PNG unless you pass type jpeg or webp, quality applies only to those two, fullPage captures beyond the viewport and omitBackground gives a transparent PNG instead of Chrome's white page. The cost is operational: Puppeteer downloads its own Chrome and needs a Node runtime that can launch a browser process, which rules out static and most shared hosting.
- import puppeteer from 'puppeteer';
- const browser = await puppeteer.launch();
- const page = await browser.newPage();
- await page.setViewport({ width: 1200, height: 630, deviceScaleFactor: 2 });
- await page.setContent(html, { waitUntil: 'networkidle0' }); // or page.goto(url, { waitUntil: 'networkidle0' })
- await page.evaluateHandle('document.fonts.ready');
- await page.screenshot({ path: 'card.png' }); // or { path: 'card.jpg', type: 'jpeg', quality: 90 }
- await browser.close();
When each method fails, and the fix
Four problems account for most mismatches between screen and file; cause and fix differ by method.
- Web fonts. The browser tool's export is an SVG loaded as an image, which cannot fetch anything, so a Google Fonts link never arrives and the text falls back to the next font in your stack (or the browser's default face). Unstyled text differs too: the exported body defaults to Arial, the preview's does not. Fix: a system font stack, or a base64 @font-face rule in Custom CSS, which applies to both. html2canvas uses the fonts the page has loaded, so await document.fonts.ready first; Puppeteer waits for the same promise, as in the sample.
- Cross-origin images. The browser tool cannot load a remote image; convert it to a data URI and paste that. html2canvas needs useCORS true and a host that sends Access-Control-Allow-Origin; without the header the image is skipped, and allowTaint true draws it but toDataURL then throws a SecurityError. Puppeteer loads images like any browser.
- foreignObject quirks. Inside the SVG your markup is parsed as XML, not HTML: void tags such as br, img and hr must be self-closed, a bare ampersand must be written as &, and HTML-only entities such as are undefined (use  ). A whole document with a doctype fails too; paste the fragment. The exported body also has 16 px of padding and only a min-height, so content below your Height is clipped although the scrolling preview shows it; set body padding 0 in Custom CSS for a full-bleed card.
- Lazy-loaded content. An img with loading lazy well below the viewport has not been fetched when Puppeteer screenshots: scroll to the bottom and call page.waitForNetworkIdle, or wait for a specific selector. html2canvas draws the DOM as it is, so an unloaded image is an empty box; the browser tool never fetches, so paste final markup with data URIs.
JPG, PNG or WebP for cards and OG images
Cards are text on flat colour, the worst input for a lossy codec: JPEG puts faint halos around crisp letters and hard edges, so for a typical card PNG is sharper and often not much larger. JPEG earns its place when a photograph or large gradient dominates, or when an uploader refuses PNG; keep quality at 90 or above, because artefacts show on text first. WebP is usually the smallest at comparable quality and keeps transparency, but some CMS uploaders still reject it.
JPEG has no alpha channel, so a transparent background never survives, and what replaces it differs by method: the browser tool warns you and fills the canvas white; Puppeteer ignores omitBackground for JPEG and gives Chrome's white page; html2canvas with backgroundColor null flattens transparent pixels onto black when you call toDataURL('image/jpeg'), so keep its default white fill. Real transparency means PNG or WEBP in the tool, a PNG screenshot with omitBackground in Puppeteer, or a PNG with backgroundColor null in html2canvas.
| Placement | Pixels | In the browser tool | Format |
|---|---|---|---|
| Open Graph (Facebook, LinkedIn, Slack) | 1200 × 630 | OG Image preset | PNG, or JPEG at 90+ over a photo |
| X / Twitter summary_large_image | 1200 × 628 | Twitter Card preset | Same as OG; one 1200 × 630 file serves both |
| Instagram or LinkedIn square post | 1080 × 1080 | Type it in (the Square preset is 800 × 800) | PNG or WebP |
| Instagram or Facebook Story | 1080 × 1920 | Story (9:16) preset | JPEG or WebP (photo-heavy) |
| YouTube thumbnail | 1280 × 720 | Type it in | JPEG or PNG |
Frequently Asked Questions
A .jpeg. The download is always named html-to-image plus the format's extension (.png, .jpeg or .webp), whatever the uploaded file was called. JPG and JPEG are the same format, so if an uploader insists on the three-letter extension, rename the file.
The preview is a normal iframe document that can fetch a linked font; the export is an SVG loaded as an image, which cannot, so the text falls back to the next font in your stack or the browser's default face. Use a system font stack or a base64 @font-face rule in the Custom CSS tab, which applies to both.
Only Puppeteer, because it is a real browser. The browser tool renders static markup in both the export and the preview iframe, which has no script permission. html2canvas runs no scripts either, but it captures a DOM your page's scripts have already built, so a finished chart is drawn.
The browser refused to load the SVG built from your markup, and the usual cause is markup that is not well-formed XML: an unclosed br, img or hr, a bare ampersand, an entity, or a whole document with a doctype. Self-close void tags, escape ampersands and paste the fragment. The message also names remote images and fonts; those more often leave a blank box in an otherwise successful download, so inline them as data URIs. A canvas too large for the browser shows a different message: Couldn't build the image — try a smaller size, or switch format to PNG.
Pick the OG Image preset and leave Scale at 2x: the layout is composed at 1200 by 630 and rasterised at 2400 by 1260, real resolution rather than upscaling. In Puppeteer set deviceScaleFactor 2 on a 1200 by 630 viewport; in html2canvas pass scale 2. Export PNG unless the card is mostly photo.
Related Tools
Paste markup, pick a preset such as OG Image 1200×630, choose PNG, JPEG or WEBP and download at 1x, 2x or 3x.
convert the exported PNG to JPG or WebPSwitch an image between JPG, PNG and WebP in your browser when an uploader insists on a different format.
compress a PNG or JPEG card before you upload itShrink the finished card so link previews and pages load fast; batch compress and download as a ZIP.
export the same HTML as a PDF with selectable textWhen you need a document rather than a picture: page size, orientation, margins and real text you can search.