How to Convert HTML to an Image
Learn three reliable ways to convert HTML into PNG, JPEG, or WebP: html2canvas, Playwright or Puppeteer, and ScreenshotNeo.

To convert HTML to an image, choose the renderer that matches your fidelity and runtime needs:
- Use html2canvas for an in-browser export of a component when approximate visual equivalence is acceptable.
- Use Playwright or Puppeteer when you need a real browser, JavaScript execution, server-side rendering, authentication, or high visual fidelity.
- Use a hosted API such as ScreenshotNeo when you want screenshots without operating browsers yourself.
The rest of this guide shows runnable implementations, explains capture options, and covers the failures that make HTML-to-image jobs unreliable.
1. Decide what “HTML to image” means
There are three different jobs commonly described as HTML conversion:

| Job | Best fit | Main limitation |
|---|---|---|
| Export a visible component in the browser | html2canvas | It reconstructs the DOM and is not a native browser screenshot. |
| Render a URL or HTML on a server | Playwright or Puppeteer | You operate browser processes and their dependencies. |
| Generate images as an application service | Hosted screenshot API | You depend on provider authentication, limits, retention and availability. |
Also choose the output boundary. A viewport screenshot captures what fits in the viewport. An element screenshot captures one CSS-selected node. A full-page screenshot includes the entire document and can become very large.
2. Convert an element in the browser with html2canvas
html2canvas traverses the DOM and builds a canvas representation. Its documentation warns that “The screenshot is based on the DOM” and may not exactly match the real rendered page. It is therefore useful for an export button, previews, invoices, and dashboards where the page is already open.
Minimal example
<button id="save">Save image</button>
<section id="card" class="card">
<h1>Monthly report</h1>
<p>Revenue increased 12%.</p>
</section>
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<script>
document.querySelector('#save').addEventListener('click', async () => {
const element = document.querySelector('#card');
const canvas = await html2canvas(element, {
backgroundColor: '#ffffff',
scale: window.devicePixelRatio,
useCORS: true
});
canvas.toBlob(blob => {
const link = document.createElement('a');
link.download = 'monthly-report.png';
link.href = URL.createObjectURL(blob);
link.click();
URL.revokeObjectURL(link.href);
}, 'image/png');
});
</script>
html2canvas(element) returns a canvas. Use toDataURL() for a data URL or toBlob() for a downloadable or uploadable binary. Set backgroundColor: null for transparency, and use scale to control output density. A high scale produces sharper images but consumes more memory.
Important html2canvas constraints
- Cross-origin images: remote images need suitable CORS response headers.
useCORS: trueasks the browser to request them with CORS, but it cannot bypass the same-origin policy. - Iframes: cross-origin iframe contents cannot be read by the page and normally will not appear correctly.
- Unsupported CSS: some browser effects, filters, blend modes, pseudo-elements, and complex layout behavior may differ from native rendering.
- Canvas limits: browser maximum dimensions and total pixel area are platform-dependent. Very tall pages can fail or produce a blank canvas; split the document into sections.
- Fonts and images: wait for
document.fonts.readyand image completion before capturing.
await document.fonts.ready;
await Promise.all([...document.images].map(img => img.complete
? Promise.resolve()
: new Promise(resolve => { img.onload = img.onerror = resolve; })));
const canvas = await html2canvas(document.querySelector('#card'));
When your page contains sensitive data, remember that this method runs in the user’s browser and the resulting pixels remain under your application’s client-side controls.
3. Render HTML with Playwright on a server
Playwright drives Chromium, Firefox, or WebKit and takes a native browser screenshot after layout and JavaScript execution. This is the better choice for server-side jobs, authenticated pages, JavaScript-heavy applications, and visual fidelity. The official screenshot documentation supports PNG, JPEG, WebP, element capture, and full-page capture.
Install and run
npm install playwright
npx playwright install chromium
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 1
});
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'page.webp', type: 'webp', fullPage: true });
await browser.close();
Capture an element, wait for content, and set state
await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-report-ready]', { state: 'visible' });
await page.evaluate(() => document.fonts.ready);
await page.locator('.report').screenshot({
path: 'report.png',
type: 'png',
animations: 'disabled'
});
Useful controls include:
fullPage: truefor the complete scrollable document.page.locator(selector).screenshot()for one element.type: 'png' | 'jpeg' | 'webp'; JPEG accepts aqualityvalue.viewport,deviceScaleFactor, color scheme, locale, timezone, and user agent through browser context options.waitForSelector, explicit delays, and network-idle waits for asynchronous content.page.setExtraHTTPHeaders(),context.addCookies(), and HTTP credentials for protected pages.
For deterministic output, disable animations with injected CSS, freeze the clock where practical, use a fixed viewport, and avoid capturing while content is still shifting. Set a navigation timeout and close the browser in a finally block so failed jobs do not leak processes.
4. Render with Puppeteer
Puppeteer provides a similar real-browser workflow around Chromium. Its Page.screenshot API supports PNG, JPEG, WebP, clipping, full-page mode, and element screenshots.
npm install puppeteer
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: 'new' });
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'networkidle2', timeout: 90000 });
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: 'page.png', fullPage: true, type: 'png' });
} finally {
await browser.close();
}
Use Puppeteer when your team already standardizes on its Chromium version or API. Use Playwright when you need multiple browser engines or its broader context and locator features. Both approaches require memory, a compatible browser binary, sandbox settings appropriate to your deployment, and a strategy for concurrent jobs.
5. Output format, sizing, and capture options
PNG, JPEG, or WebP
- PNG: lossless UI text, diagrams, and transparency.
- JPEG: smaller photographic images where some loss is acceptable; no transparency.
- WebP: efficient delivery when all consumers support it.
Viewport versus full page
Viewport captures are predictable and cheap to display. Full-page captures can include long feeds, lazy-loaded images, and large blank regions. Measure the resulting dimensions and reject or split documents that exceed your consumer’s limits.
Lazy content and scrolling
Full-page rendering does not guarantee that every lazy image has loaded. In a real browser, scroll through the page or wait for a page-specific ready selector before taking the shot. A page can report network idle while an intersection observer has not yet requested below-the-fold images.
Authenticated and personalized pages
Pass cookies, headers, authorization, or a user agent only through a controlled backend. Never put long-lived credentials in a client-side download button. Redact secrets from logs and avoid caching personalized output.
6. Or skip the browser setup
ScreenshotNeo accepts one GET request and returns PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for the full parameter list.

cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo can load lazy images for full-page captures, capture one CSS-selected element, emulate dark mode and 12 device presets, set any viewport and retina scale, add custom CSS or JavaScript, click an element, wait for a selector, delay, or network idle, and hide selectors. It can also block ads, trackers, requests, or resource types; send custom headers, cookies, user agents, and Authorization; set timezone and geolocation; create transparent images; resize output; cache with a TTL you choose; generate signed links for public image tags; run asynchronous jobs with signed webhooks; capture up to 100 URLs per bulk call; and expose usage data and an OpenAPI specification. Common parameter names used by other screenshot APIs also work, which simplifies migration.
Before capture it accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and whether the shot was billed.
An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Pricing starts with 1,000 shots per month free with no card; paid plans are Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free and every feature is on every plan. Create a free ScreenshotNeo account and start with 1,000 screenshots a month at no charge.
7. Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Remote image is missing in html2canvas | CORS headers are absent or the image is cross-origin. | Serve the asset with CORS headers, proxy it through your origin, or use a real-browser server capture. |
| Text uses the wrong font | Capture started before web fonts finished. | Await document.fonts.ready and verify the font request completed. |
| Screenshot shows a loading skeleton | JavaScript data had not arrived. | Wait for a stable selector or application-ready flag instead of relying only on a short delay. |
| Full-page image is blank or crashes | Canvas or image dimensions exceeded browser limits. | Reduce scale, capture sections, or use a server renderer and split the output. |
| Playwright times out | Navigation, a third-party request, or a never-ending connection prevented the wait condition. | Set a bounded timeout, use domcontentloaded plus explicit readiness checks, and block nonessential requests. |
| Authenticated page redirects to login | Cookies or authorization were not supplied in the browser context. | Set the correct cookies or headers before navigation and confirm the target host and path. |
| Output changes between runs | Animations, responsive layout, time-dependent content, or late network requests. | Fix viewport and device scale, disable animation, freeze data where possible, and wait for readiness. |
| ScreenshotNeo response is not billed | The page verdict was a bot check, blank page, timeout, failed load, or cache hit. | Inspect X-Page-Verdict and X-Billed; adjust access, waiting, or cache settings only when appropriate. |
8. Performance, reliability, and cost
Performance
Browser startup is usually the largest avoidable cost in self-hosted rendering. Keep a browser process warm, reuse contexts where isolation permits, limit concurrency to available memory, block analytics and advertising requests, and avoid unnecessary full-page captures. PNG encoding and very large full-page bitmaps also consume CPU and memory.
html2canvas avoids a server round trip but competes with the user’s device and can freeze the tab for large documents. A hosted API moves browser operations out of your application and lets you choose caching, asynchronous jobs, and bulk capture according to workload.
Reliability
Define a readiness condition for each page. Record the URL, viewport, format, timing, verdict, and failure reason. Retry transient navigation failures with a limit and backoff; do not blindly retry deterministic 4xx responses or bot challenges. For asynchronous jobs, make webhook handling idempotent and verify signatures before storing results.
Cost
Self-hosting trades per-image service charges for infrastructure, browser patching, queueing, storage, and engineering time. Hosted pricing should be compared using successful images, because retries, failed loads, and cache behavior can change effective cost. ScreenshotNeo bills only clean shots; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed.
9. A practical implementation checklist
- Choose client DOM rendering, a real browser, or a hosted API.
- Specify viewport, device scale, output format, and whether the target is an element or full page.
- Wait for fonts, images, asynchronous data, and lazy content.
- Resolve CORS, iframe, authentication, and bot-check behavior before production.
- Set navigation and job timeouts; close browser resources in cleanup code.
- Limit image dimensions and memory use; split very long pages.
- Log verdicts and failures without exposing cookies or authorization headers.
- Cache stable public pages and avoid caching personalized captures.
- Test PNG, JPEG, and WebP consumers independently.
10. FAQ
Can I convert an HTML string instead of a URL?
Yes. With Playwright or Puppeteer, call page.setContent(html) before taking the screenshot. Include CSS, wait for fonts and images, and use a base URL or absolute asset URLs for external resources.
Which method gives the most accurate screenshot?
A real browser generally gives higher fidelity than DOM reconstruction because it uses the browser layout engine and executes page JavaScript. Playwright and Puppeteer are the usual server-side choices.
Can HTML be converted directly to PDF?
Yes. Playwright and Puppeteer expose PDF generation, and ScreenshotNeo supports PDF output with paper size, margins, landscape mode, and page ranges.
Why is my screenshot different on mobile?
Responsive CSS depends on viewport width, device scale, user agent, and sometimes touch or geolocation state. Set those values explicitly and capture at the same dimensions used by the target device.
Should I use a data URL or upload the image?
Data URLs are convenient for small, short-lived browser previews. Use a Blob, object storage, or a streamed response for larger images and server workflows.


