October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
Chromium

How to Convert HTML to PNG in Rust with Headless Chrome

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To convert HTML to PNG in Rust, render the page in a real browser and save the browser’s screenshot bytes. The most direct Rust approach is the headless_chrome crate (version 1.0.22 in its current docs), which drives Chrome or Chromium through the DevTools Protocol. You navigate to a URL, wait until the page is ready, call the PNG capture method, and write the returned bytes to a file.

This method works for ordinary URLs and JavaScript applications. For HTML held in memory, first expose it through a local server, a suitable local URL, or a data URL; navigate_to expects a URL rather than an HTML string.

What you need before writing Rust code

  • A Rust toolchain capable of building your application.
  • Chrome or Chromium installed on the machine that will run the program.
  • A target URL, or a way to serve your generated HTML locally.
  • A deterministic viewport and a page-specific readiness condition if the page loads JavaScript, images, fonts, or other remote assets.

headless_chrome can optionally fetch a known-good Chromium binary with its fetch feature. In production, installing and pinning a browser package yourself often gives you clearer control over upgrades, security patches, and container images. The crate is synchronous and does not expose every Puppeteer or DevTools capability; if you need asynchronous WebDriver control or browsers other than Chrome, compare it with the project’s documented fantoccini alternative.

Convert a URL to PNG with headless_chrome

1. Create the project and add the crate

cargo new html-to-png
cd html-to-png
cargo add [email protected]

You can also add the dependency manually:

[dependencies]
headless_chrome = "1.0.22"

Install Chrome or Chromium using your operating system’s package manager, or use the crate’s optional browser-download feature where that fits your deployment policy. Verify the binary starts normally before debugging Rust code.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

2. Capture the page

use headless_chrome::{protocol::cdp::Page, Browser};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let browser = Browser::default()?;
    let tab = browser.new_tab()?;

    tab.navigate_to("https://example.com")?;
    tab.wait_for_element("body")?;

    let png = tab.capture_screenshot(
        Page::CaptureScreenshotFormatOption::Png,
        None,
        None,
        true,
    )?;

    std::fs::write("output.png", png)?;
    Ok(())
}

Run it with cargo run. The program opens a headless browser, navigates to the URL, waits for a body element, captures PNG bytes, and writes output.png in the project directory. The final Boolean and optional arguments belong to the crate’s screenshot API; keep them aligned with the exact crate version you compile.

3. Wait for the content that matters

Waiting for body only proves that the document has a body element. A client-rendered application may still be blank or incomplete. Prefer a selector that appears after your app has finished rendering:

tab.navigate_to("https://app.example.test/report")?;
tab.wait_for_element("#report-ready")?;
let png = tab.capture_screenshot(
    Page::CaptureScreenshotFormatOption::Png,
    None,
    None,
    true,
)?;

For pages with delayed images, fonts, or animations, use the crate’s JavaScript interaction and waiting facilities to establish an application-specific ready state. A fixed sleep can be useful as a last resort, but a selector or state check is usually more reliable because it adapts to variable network speed.

Control viewport, page bounds, and element output

Choose dimensions deliberately

Screenshot dimensions depend on the browser viewport, device scale, CSS, and the capture bounds you pass. Set these deliberately when a downstream system expects a predictable image size. Do not assume that a default capture is a full-page image or that every page will have identical pixel dimensions; verify the behavior for your crate and Chromium versions.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Capture one element instead of the whole page

When you need a chart, invoice, or card, wait for its selector and use the element handle’s screenshot method:

use headless_chrome::{protocol::cdp::Page, Browser};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let browser = Browser::default()?;
    let tab = browser.new_tab()?;
    tab.navigate_to("https://example.com/dashboard")?;

    let chart = tab.wait_for_element("#chart")?;
    let png = chart.capture_screenshot(Page::CaptureScreenshotFormatOption::Png)?;
    std::fs::write("chart.png", png)?;
    Ok(())
}

Element capture is useful when a full-page screenshot includes navigation, cookie notices, or unrelated content. The selected element must exist and be laid out before capture.

HTML generated in memory

The browser still needs a navigable address. Common patterns are:

  • Start a small local HTTP server and navigate to http://127.0.0.1:PORT/.
  • Write a temporary HTML file and navigate to a local file URL, subject to browser security and resource-loading rules.
  • Encode a self-contained document as a data:text/html,... URL. External scripts, fonts, and images may be blocked or unavailable, so a local server is generally easier to reproduce.

Serving HTML locally also lets you use relative URLs and inspect network failures in a way that resembles production.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Chromium without a Rust browser crate

For a one-off URL, Chromium’s command line is simpler than embedding a browser controller:

chrome --headless --disable-gpu --screenshot --window-size=1280,900 https://example.com

The documented default output is screenshot.png in the current working directory. This is a viewport screenshot; full-page output requires additional steps and should not be assumed from the command alone. A Rust application can invoke the command with std::process::Command when it only needs orchestration:

use std::process::Command;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let status = Command::new("chrome")
        .args([
            "--headless",
            "--disable-gpu",
            "--screenshot=output.png",
            "--window-size=1280,900",
            "https://example.com",
        ])
        .status()?;

    if !status.success() {
        return Err("Chromium returned a failure status".into());
    }
    Ok(())
}

For lower-level control, launch Chromium with --headless --remote-debugging-port=9222 and speak DevTools Protocol yourself. That gives more control but shifts browser lifecycle, protocol messages, and error handling into your application.

Headless Chrome version and deployment caveats

Chromium’s headless packaging is changing. The current Chromium headless documentation says that from M132, the old headless implementation is no longer part of the regular Chrome binary; --headless=old has no effect. If your deployment depends on the legacy shell, migrate to the separately distributed chrome-headless-shell or use the current documented mode for your installed binary.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Pin the browser version in CI or containers and record the Rust crate version alongside it. Browser upgrades can alter font availability, layout, security policy, and screenshot pixels. Use a non-root user where possible, provide a writable temporary directory, and allocate enough shared memory for pages with large canvases or many images. Treat untrusted URLs as hostile: isolate the browser, restrict network access, and avoid passing secrets in page URLs.

Common failures and fixes

Chrome cannot be found

Symptom: Browser::default() fails while launching. Fix: install Chrome/Chromium, put its executable on PATH, or configure the crate for the browser location supported by your version. In containers, install the matching system packages and fonts.

The PNG is blank or missing application data

Cause: capture happened before JavaScript, images, or fonts finished. Fix: wait for a selector that represents completed rendering, trigger required interactions, and make sure API requests are reachable from the browser environment.

Waiting for a selector times out

Cause: the selector is incorrect, hidden behind authentication, or never inserted because a script failed. Fix: open the same URL manually, inspect the DOM and console, verify credentials and network access, and choose a selector that is actually present in the success state.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Local HTML cannot load its assets

Cause: file URLs, relative paths, CORS, or blocked mixed content. Fix: serve the document over a local HTTP server, use absolute asset URLs where appropriate, and keep all required resources reachable.

Images differ between machines

Cause: different Chromium builds, fonts, device scale, timezone, or late network responses. Fix: pin the browser and fonts, set viewport-related values explicitly, wait for images and fonts, and compare in the same container image.

Full-page capture is unexpectedly short

Cause: the chosen API call captured the viewport or bounds rather than the full document. Fix: use the crate’s documented bounds and full-page configuration for your version, or calculate a page height after layout and capture that region. Test long pages with lazy-loaded content separately.

Which approach should you choose?

Approach Best for Trade-offs
headless_chrome Rust applications needing selectors, JavaScript interaction, and element screenshots Requires a managed browser; synchronous API; does not implement every Puppeteer feature
Chromium CLI One URL, fixed viewport, minimal orchestration Less control over readiness, elements, and multi-step interaction
DevTools Protocol directly Specialized automation and protocol-level control More code for sessions, events, errors, and browser lifecycle
WebDriver via fantoccini Asynchronous workflows or browser portability Different API model and an additional WebDriver/browser setup
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo is a hosted screenshot API and MCP server. One GET request renders a URL and returns PNG, JPEG, WebP, or PDF. It accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Rust can call the same endpoint with any HTTP client. The API parameters commonly used by other screenshot services also work:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for capture options such as full-page output, CSS selectors, dark mode, device presets, retina scale, custom JavaScript and CSS, waits, request blocking, cookies, headers, timezone, geolocation, resizing, caching, signed links, asynchronous webhooks, bulk capture, and usage reporting. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Plans include 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000 shots. Higher plans are Starter $5/3,000, Growth $15/15,000, Pro $39/60,000, Scale $99/250,000, and Business $249/1,000,000; annual billing provides two months free, and every feature is included on every plan.

Create a free ScreenshotNeo account to use the monthly allowance without a card.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Does converting HTML to PNG preserve JavaScript-generated content?

Yes, when a browser engine renders the page and you wait for the application’s ready state before capturing. A parser-only HTML library cannot reproduce browser layout or client-side rendering.

Can I return PNG bytes directly from a Rust web service?

Yes. Keep the byte vector returned by the screenshot call in memory and send it with an image/png response, provided your service controls browser lifetime, timeouts, and resource limits.

Is a headless screenshot deterministic?

Only when the browser version, fonts, viewport, device scale, timezone, content, and readiness conditions are controlled. Remote data and animations can still change pixels.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.