ScreenshotNeo

BlogComparisons

Best Open-Source Tools for Converting HTML to Images Offline

Compare Playwright, Puppeteer, and Openkova for local HTML screenshots. Learn what offline really requires, how to capture pages, and how to troubleshoot.

By the ScreenshotNeo team4 October 20269 min read

Short answer: Playwright is the best starting point for most developers who need programmable, local HTML-to-image capture: its documented screenshot API supports full-page, element, and in-memory captures. Puppeteer is a strong fit for projects already using its browser automation API. Openkova describes a self-hosted service for HTML snippets, files, and URLs, but verify its installation and disconnected-runtime requirements before relying on it offline.

“Offline” needs a precise boundary. Running the renderer locally does not mean installation can happen without a network, or that a page will render without network access. Browser binaries, packages, fonts, scripts, images, and other resources all matter. The sources below establish capture capabilities, not a guarantee that every tool can be installed and run in a fully disconnected environment.

1. Choose by the job

Tool Best fit Documented capture options Offline caveat
Playwright Programmable, repeatable screenshots with capture controls File output, full-page, selected element, image buffer, format, clip area, quality Local execution is possible, but offline installation and all page dependencies must be checked for your setup.
Puppeteer Projects already built around Puppeteer and its browser automation API Page screenshots and element screenshots Check the install guide and browser packaging for the version you deploy; local capture alone does not prove disconnected setup.
Openkova A self-hosted application interface instead of building a capture flow directly into your code The project describes HTML snippets, uploaded files, and URLs as inputs, with PNG and JPEG outputs Those are project claims. Verify repository health, security, license, and offline installation requirements before adopting it.

For browser-accurate rendering with code-level control, start with Playwright. Prefer Puppeteer when it already fits your codebase. Consider Openkova when a service wrapper is useful and you have verified the operational requirements. Do not choose a renderer solely because its conversion command runs locally.

2. What “offline” means in practice

HTML-to-image is a rendering task: a browser or other rendering engine lays out markup, styles, and resources, then captures pixels. To work without a network, the complete path must be local:

  • Installation: The language package, browser binary, and any system libraries must already be available or installable from an offline package cache.
  • Runtime assets: Fonts, stylesheets, images, scripts, and data requests must be local or intentionally omitted. A page with remote resources may render incompletely when disconnected.
  • Input: A local HTML file or string is straightforward; a URL may still point to a network resource even when the screenshot program runs on your machine.
  • Reproducibility: Record the tool and browser versions, operating system, fonts, viewport, and relevant rendering settings. Browser screenshots can vary with OS, version, settings, hardware, and headless mode.

Before deployment, test with networking disabled and inspect the output for missing assets. That validates your specific installation and page; it does not establish that every release or input can work disconnected.

3. Playwright: a practical local workflow

The example below uses Node.js and the Playwright package. Install the package and its browser while connected, then reuse the installed environment offline. For genuinely disconnected installation, prepare and verify the package and browser artifacts in advance using the official installation instructions.

npm init -y
npm install --save-dev playwright
npx playwright install chromium

Save this as capture.mjs. It loads a local HTML file, waits for the page to load, captures a full-page PNG, and also shows how to capture one element or retrieve PNG bytes.

import { chromium } from 'playwright';
import { pathToFileURL } from 'node:url';

const input = process.argv[2] ?? './index.html';
const output = process.argv[3] ?? './page.png';
const selector = process.argv[4];

const browser = await chromium.launch({ headless: true });
try {
  const page = await browser.newPage({
    viewport: { width: 1440, height: 900 },
    deviceScaleFactor: 1
  });
  await page.goto(pathToFileURL(input).href, { waitUntil: 'load' });
  await page.evaluate(() => document.fonts.ready);

  if (selector) {
    const element = page.locator(selector);
    await element.waitFor({ state: 'visible' });
    await element.screenshot({ path: output, type: 'png' });
  } else {
    await page.screenshot({ path: output, type: 'png', fullPage: true });
  }

  // Use bytes when the image should stay in memory instead of being saved.
  const pngBytes = await page.screenshot({ type: 'png' });
  console.log(`Saved ${output}; in-memory PNG is ${pngBytes.length} bytes.`);
} finally {
  await browser.close();
}

Run it with node capture.mjs ./index.html ./page.png, or capture an element with node capture.mjs ./index.html ./card.png "#product-card". The official Playwright screenshot guide documents file, full-page, buffer, and element screenshots. Its screenshot API also accepts image-format, clip-area, and quality parameters; consult the installed version’s API docs for exact supported values.

Playwright capture choices

  • Full page: fullPage: true captures the scrollable page. Very long pages can produce large images and may expose lazy-loading behavior; make sure needed content has loaded before capture.
  • Element: Locate a selector and call screenshot() on it. Wait for visibility and handle a missing selector explicitly in production.
  • Buffer: Omit path to receive image bytes, useful for pipelines that upload or transform output without writing an intermediate file.
  • Format and quality: The API documents format and quality controls. Use a lossless format for sharp text or when downstream processing needs exact pixels; choose a lossy format only when its size/quality tradeoff suits the output.
  • Clip: Capture a defined rectangle when only a region is needed. Coordinate the clip with the viewport and page layout.

4. Puppeteer: capture with Node.js

Puppeteer provides a similar browser automation workflow. Install its package and browser according to the official instructions for the version you intend to use. The current documentation inspected for this article displayed version 25.12.0; confirm commands and packaging against the version you install.

npm init -y
npm install --save-dev puppeteer

Save as capture-puppeteer.mjs. This example loads a local file and captures either the whole page or a selected element.

import puppeteer from 'puppeteer';
import { pathToFileURL } from 'node:url';

const input = process.argv[2] ?? './index.html';
const output = process.argv[3] ?? './page.png';
const selector = process.argv[4];

const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
  await page.goto(pathToFileURL(input).href, { waitUntil: 'load' });
  await page.evaluate(() => document.fonts.ready);

  if (selector) {
    const element = await page.$(selector);
    if (!element) throw new Error(`No element matched selector: ${selector}`);
    await element.screenshot({ path: output, type: 'png' });
  } else {
    await page.screenshot({ path: output, type: 'png', fullPage: true });
  }
} finally {
  await browser.close();
}

Run node capture-puppeteer.mjs ./index.html ./page.png or provide a selector as the third argument. See the official Puppeteer screenshots guide and Page.screenshot API for the options supported by your installed release.

5. Openkova: a self-hosted wrapper

Openkova describes an open-source, self-hostable HTML-to-screenshot application powered by Puppeteer and headless Chromium. Its homepage lists HTML snippets, uploaded files, and URLs as inputs, with PNG and JPEG among its outputs. That makes it worth investigating if you want an application interface rather than embedding browser automation in your own program.

These are project descriptions, not independent verification of maintenance, security, licensing, or disconnected operation. Review the project’s current setup instructions and run an isolated offline test before making it part of a production workflow. The research for this guide does not establish a stable API contract or a verified install command, so no such command is presented here.

6. cURL, Python, and Node.js when the input is a URL

For a URL that is reachable from the machine running the browser, these are minimal browser-automation examples. They use Playwright’s official language APIs. The HTML file examples above are more appropriate when the content itself must remain local.

cURL through Playwright’s CLI

cURL does not render HTML. One option is to use Playwright’s command-line screenshot command from a shell; exact CLI options should be checked against the installed Playwright version.

npx playwright screenshot --full-page https://example.com page.png

Python with Playwright

python -m pip install playwright
python -m playwright install chromium
from pathlib import Path
from playwright.sync_api import sync_playwright

url = 'https://example.com'
output = Path('page.png')

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    try:
        page = browser.new_page(viewport={"width": 1440, "height": 900}, device_scale_factor=1)
        page.goto(url, wait_until='load', timeout=60_000)
        page.screenshot(path=str(output), full_page=True, type='png')
    finally:
        browser.close()

Node.js with Playwright

import { chromium } from 'playwright';

const browser = await chromium.launch({ headless: true });
try {
  const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
  await page.goto('https://example.com', { waitUntil: 'load', timeout: 60_000 });
  await page.screenshot({ path: 'page.png', fullPage: true, type: 'png' });
} finally {
  await browser.close();
}

These examples require the browser binaries and runtime dependencies to be present. For a fully offline capture, replace the URL with a local file or a locally served page, and ensure the page does not fetch remote assets.

7. Troubleshooting

Symptom Likely cause Fix
Browser executable missing The package is installed but its browser binary is not present in the environment. Install the browser while connected or provision the correct browser artifact and dependencies in your offline image. Verify the path/version used at runtime.
Blank or incomplete screenshot Navigation failed, rendering is delayed, or required resources are remote and unavailable. Check navigation errors and console output; confirm local resource paths; wait for the specific content or fonts your page needs.
Fonts or images differ Assets were not installed, paths are wrong, or the page relies on remote resources. Bundle fonts and images locally, validate file URLs, and wait for fonts with document.fonts.ready.
Element capture fails The selector does not match, is hidden, or has not appeared yet. Validate the selector, wait for visible state, and report a clear error when no match exists.
Capture cuts off content The viewport capture was used instead of full-page capture, or the page changes size during rendering. Use the full-page option for a page capture; wait for layout-changing content before taking the screenshot.
Screenshot changes across machines Browser, OS, fonts, hardware, settings, or headless mode differ. Pin tool/browser versions, standardize fonts and viewport, and capture in a consistent environment.
Install fails with network disabled Package manager or browser installation is trying to fetch missing artifacts. Prepare dependency and browser caches while online, or use an approved internal mirror; verify the resulting deployment with networking disabled.

8. Performance, reliability, and cost

  • Performance: Browser startup, page complexity, fonts, and image loading all affect capture time. Reuse a browser process for multiple captures in a controlled worker, while keeping pages isolated as appropriate; close pages and browsers reliably to avoid resource leaks.
  • Reliability: Set navigation timeouts, detect failed navigation, wait for the content that matters, and always close the browser in a finally block. For dynamic pages, a fixed delay can be less reliable than waiting for a specific selector or application-ready condition.
  • Output size: Full-page and high-density captures use more memory and disk space. Choose viewport and output format based on the use case, and avoid capturing more pixels than required.
  • Cost: Open-source libraries do not charge per screenshot, but running browsers has infrastructure and engineering costs: CPU, memory, storage, dependency maintenance, and time spent debugging rendering differences. A self-hosted wrapper adds service operations and update work.
  • Version control: Pin the automation package and browser version in reproducible environments. Recheck the relevant official docs when upgrading because installation and API details can change.

9. Or skip the browser setup

If you need a hosted screenshot API instead of maintaining browser binaries, ScreenshotNeo takes a URL in one GET request and returns an image or PDF. Its API documentation is at screenshotneo.com/docs.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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}`);
  • Cookie banners are accepted and removed before the shot; newsletter popups and chat widgets are removed too. Each cleanup step can be turned off.
  • Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Responses identify the page verdict and billing status in headers.
  • An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
  • The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

10. FAQ

Can I convert HTML to an image with no internet at all?

Yes, if the browser, its required system dependencies, and every page asset are available locally. Validate both installation and capture with networking disabled.

Which tool should I try first?

Try Playwright for a new programmable workflow with full-page, element, and buffer capture. Choose Puppeteer when it better fits an existing Puppeteer project.

Does cURL convert an HTML file to an image?

No. cURL transfers data; it does not render HTML. Use a browser automation library or a screenshot service for rendering.

Is Openkova proven to install fully offline?

The cited project homepage describes a self-hostable screenshot application and its input/output modes. The research available for this article does not establish disconnected installation behavior.

Sources