How to Convert HTML to an Image in Bash
Convert a URL or local HTML file to an image from Bash with Playwright, Chrome Headless, or wkhtmltoimage, with complete commands and troubleshooting.

Short answer: Bash can launch an HTML renderer, but it cannot render a page itself. For modern pages and repeatable jobs, use Playwright with a small Node.js script; for a one-off URL, Chrome Headless offers a short command. For a local HTML file, wkhtmltoimage input.html output.png is a direct converter interface, but check its output against your page before relying on it.
This guide covers a runnable Playwright workflow, CLI and Chrome alternatives, local files, output and wait options, common failures, and when a hosted API such as ScreenshotNeo can remove browser setup from the job.
1. Use Playwright for repeatable captures
Playwright gives a script control over navigation and screenshot settings, including output path and full-page capture. Bash passes the URL and destination to the script. This is useful for reports, scheduled captures, and build jobs where a browser-rendered result matters.
Install the project dependency and browser
From a project directory with Node.js available, install Playwright and its Chromium browser. Follow the official installation instructions for the operating system and dependencies in your environment; browser binaries and system libraries are part of the setup.
npm install playwright
npx playwright install chromium
Playwright documents browser installation and operating-system dependencies in its browser guide. If the environment requires dependency installation, consult that guide for the appropriate command.
Create the capture script
Save this as capture.cjs. It accepts a URL and optional output path. It defaults to a 1280 × 900 viewport and captures the full page. The script closes Chromium in a finally block so a navigation or screenshot error does not leave the browser process running.
const { chromium } = require('playwright');
(async () => {
const url = process.argv[2];
const output = process.argv[3] || 'page.png';
if (!url) {
console.error('Usage: node capture.cjs <url> [output.png]');
process.exitCode = 2;
return;
}
const browser = await chromium.launch();
try {
const page = await browser.newPage({
viewport: { width: 1280, height: 900 }
});
const response = await page.goto(url, {
waitUntil: 'networkidle',
timeout: 60000
});
if (response && !response.ok()) {
console.error(`Page returned HTTP ${response.status()}`);
}
await page.screenshot({ path: output, fullPage: true });
console.log(`Saved ${output}`);
} finally {
await browser.close();
}
})();
Run it from Bash with a quoted URL so shell characters are not treated as syntax:
node capture.cjs 'https://example.com' 'page.png'
The Playwright screenshot API describes full-page capture and image options. The example uses networkidle as a starting point, not a universal readiness signal: pages with persistent connections or frequent background requests may never become idle. See the wait guidance below and choose a condition that matches the page.
Make it a reusable Bash command
A small shell wrapper makes repeated invocations easier. Save as capture.sh, then run chmod +x capture.sh. Pass the URL and optionally an output filename.
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -lt 1 || $# -gt 2 ]]; then
printf 'Usage: %s <url> [output.png]\n' "$0" >&2
exit 2
fi
url=$1
output=${2:-page.png}
node "$(dirname "$0")/capture.cjs" "$url" "$output"
./capture.sh 'https://example.com' 'report.png'
The wrapper uses strict shell settings and quotes both arguments. That matters for URLs containing ampersands, question marks, or other characters that shells may interpret.
2. Choose the right renderer and capture scope
| Method | Best fit | What to consider |
|---|---|---|
| Playwright API | Recurring automation, browser scripting, full-page or controlled captures | Install Node.js, Playwright, a browser binary, and environment dependencies. |
| Playwright CLI | Command-oriented workflows and interactive use | Its screenshot command supports output path, image type, full-page, and high-resolution options; consult the current CLI reference for syntax. |
| Chrome Headless | One-off URL screenshot with a direct command | Chrome must be installed and available under the command name used in your environment. |
| wkhtmltoimage | Direct local input-to-output conversion | Check the rendered result for your specific page, especially if it relies on modern JavaScript or complex layout. |
Use the Playwright API when the job needs an explicit viewport, a readiness condition, or script logic. Use its CLI when the documented command-line controls are enough. Chrome Headless is concise for a quick URL capture. The direct wkhtmltoimage interface is convenient for local HTML, but the available research does not establish its compatibility with current script-heavy sites.
Full page versus viewport
A viewport screenshot shows the visible browser area; a full-page screenshot attempts to include the page’s full vertical content. Full-page captures can be very tall and consume more memory than a viewport image. Lazy-loaded images may not appear if the page only loads them as they approach the viewport. If complete content matters, scroll through the page or wait for the relevant elements before capture, and verify the resulting image.
Output format, quality, and dimensions
Playwright’s screenshot API provides format and quality controls as well as full-page capture. PNG is a lossless default suited to text and sharp edges. JPEG is often a smaller choice for photographic content, with quality trading file size against image artifacts. WebP may suit pipelines whose downstream tools accept it. Choose output dimensions deliberately: larger viewports and high-resolution scaling produce more pixels, larger files, and greater resource use. Check the current API reference for supported option names and constraints.
3. Other command-line routes
Chrome Headless for a URL
When Chrome is installed, this captures a URL using a 1280 × 900 window and writes screenshot.png in the current directory:
chrome --headless --screenshot --window-size=1280,900 'https://example.com'
Chrome’s Headless reference documents --screenshot and use with --window-size. The exact executable name may differ by installation; use the name provided by your system. To keep outputs organized, run the command from the desired output directory. Consult the Chrome reference for any additional flags supported by the installed version.
Playwright CLI
Playwright’s CLI documents screenshot commands for a URL, including a custom filename, image type, full-page capture, and high-resolution capture. Check the Playwright CLI documentation for the current syntax and install requirements. Choose this route when you want a command workflow and do not need the additional control of a custom script.
Convert a local HTML file with wkhtmltoimage
The documented input/output shape is simple:
wkhtmltoimage input.html output.png
For a local file, the installed converter needs to be able to read the file and any local assets it references. If images, stylesheets, or fonts are missing, inspect their paths and permissions. The wkhtmltoimage manual describes the tool as converting an HTML page into an image and documents its command interface. Rendering behavior depends on the installed binary and page; compare its result with a browser if fidelity matters.
HTML source is not the rendered page
Capturing a URL means rendering the page in a browser engine, including scripts that may change the document. Chrome’s --dump-dom parses the response, runs scripts, and serializes the resulting DOM; it is not simply a copy of the original response source. A screenshot reflects rendered pixels, so diagnose layout differences in the browser and its loaded resources, not only in the raw HTML.
4. Make captures reliable
- Pick an explicit viewport. Use a stable width and height so responsive breakpoints produce consistent output.
- Wait for the page state you need. Network idle can hang on pages with persistent traffic. A fixed delay can be wasteful and still miss slow content. When possible, wait for a page-specific selector that indicates the content is ready.
- Handle navigation failures. Set a timeout and report navigation or HTTP errors. A page can return an error status and still render an error page, so decide whether such pages should produce an image or fail the job.
- Keep output paths deterministic. Create the destination directory before capture and use unique names in parallel jobs to avoid overwrites.
- Close browser processes. Use cleanup logic even when a screenshot fails, particularly in long-running workers.
- Check fonts and assets. A different machine may lack fonts or network access used by the page. Install the required fonts where permitted, and ensure the capture environment can load the assets.
- Inspect the image. A successful process exit only means the command completed; it does not prove that the correct content loaded or that overlays are absent.
For diagnosis, Chrome’s --dump-dom can help reveal the post-script DOM, but it does not create an image. The browser and page state remain the source of screenshot behavior.
5. Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
browserType.launch cannot find executable |
Playwright package is installed but its browser binary is not. | Run the documented Playwright browser installation command for Chromium and confirm the environment has required OS dependencies. |
chrome: command not found |
Chrome is not installed under that executable name or is absent from PATH. |
Install or locate Chrome for the environment, then use its actual binary name or full path. |
| Navigation times out | The site is slow, unreachable from the job, or never reaches the selected wait state. | Check network access and the URL. Increase timeout only when justified; use a page-specific readiness condition instead of waiting for all network activity to stop. |
| Screenshot is blank or incomplete | The page did not finish rendering, an error or bot-check page was served, or lazy content was never loaded. | Inspect the rendered state, wait for a meaningful selector, and confirm the capture environment can access the page and resources. |
| Full-page output is unexpectedly huge | The document is very tall, possibly due to long content or layout issues. | Capture the viewport, target a specific element, or split the page into intentional sections if the downstream use permits. |
| Local images or styles are missing | Paths are wrong, files are inaccessible, or the renderer cannot resolve relative resources. | Use valid paths and ensure assets are available from the local file or URL context. Inspect the HTML’s references. |
| Output is overwritten | Multiple jobs share a fixed output filename. | Give each invocation a unique output path and create the destination directory before running the capture. |
| Screenshot differs between machines | Viewport, browser version, fonts, assets, or page content differs. | Pin the viewport and browser installation where practical, provision fonts and network access, and compare the page state before capture. |
6. Performance, reliability, and cost
Self-hosted browser capture has no per-image API charge, but it uses your CPU, memory, storage, and engineering time. Browser startup and page loading often dominate a single capture. For batches, reusing a browser process can avoid repeated startup overhead, while isolating pages or workers helps contain failures; size concurrency according to available memory and page complexity. Very tall or high-resolution images increase processing and file storage.
Reliability depends on both the renderer and the target site: DNS and network access, external assets, page scripts, fonts, authentication, consent banners, bot checks, and changing page content all affect output. Treat screenshots as generated artifacts: use deterministic filenames, retain only what the workflow needs, and control access when captures may contain account or personal information.
For occasional captures, local browser setup may be the simplest cost model. For recurring jobs, compare the cost of maintaining browser binaries and workers with a hosted screenshot service. Also account for what happens on failed loads and whether the service exposes a reason for each result.
7. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns a PNG, JPEG, WebP, or PDF. Its documented options cover full-page and selector captures, device and viewport settings, dark mode, custom CSS and JavaScript, wait conditions, request blocking, cookies and headers, caching, async jobs, bulk capture, and more. See the ScreenshotNeo API documentation for parameters and response details.

curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Equivalent Python and Node.js requests:
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)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
Before capture, ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify page verdict and billing status. Its MCP server gives AI agents tools named take_screenshot, get_page_info, and capture_pdf.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan. That makes it an option when avoiding browser installation, removing common overlays, understanding billed outcomes, or letting an MCP client capture pages matters.
Sign up for 1,000 free screenshots a month, with no card required.
8. Frequently asked questions
Can Bash convert HTML to an image by itself?
No. Bash runs a renderer or browser command and passes it arguments; the browser or converter creates the image.
Which method should I use for modern JavaScript-heavy pages?
Start with a browser renderer such as Playwright or Chrome Headless, then check the output. The available documentation does not establish current compatibility for every page or converter version.
Can I capture a private page?
A browser workflow can be configured for authenticated pages, but protect credentials and resulting images. Do not place sensitive captures in shared artifacts unless their access controls are appropriate.
Why is a screenshot different from the HTML file?
The screenshot is the rendered page at a particular viewport and time. Scripts can change the DOM, and fonts, images, styles, and network responses affect the pixels.


