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

Use a real browser when your HTML depends on modern CSS, web fonts, images, or JavaScript. In Go, chromedp gives direct Chrome DevTools Protocol control, while Playwright Go offers a higher-level page API. Use go-webengine when a CGO-free deployment is more important than complete browser compatibility. The examples below show viewport, full-page, and element PNG captures, deterministic waits, and deployment safeguards.

Choose the renderer first

HTML-to-PNG is a rendering problem, not a string-conversion problem. A browser must parse HTML, apply CSS, resolve fonts and images, run JavaScript, perform layout, and paint pixels. Your choice determines how closely the result matches Chrome and how much infrastructure your service needs.

Approach Rendering target Runtime requirement Best fit Main trade-off
chromedp Chrome/Chromium through CDP Chrome, Chromium, or a headless-shell image Maximum Chrome fidelity and direct protocol control You manage browser binaries, sandboxing, startup, patching, and concurrency
Playwright Go Chromium page and browser APIs Playwright-compatible browser installation Readable automation, contexts, and page lifecycle controls Browser downloads and version pinning are part of deployment
go-webengine Pure-Go HTML/CSS/DOM/JavaScript engine CGO-free Go process; no Chromium Small, self-contained deployments where your pages fit its supported subset Compatibility must be tested for your CSS, fonts, SVG, images, and scripts

There is no published controlled throughput or memory benchmark that makes one option universally fastest. Browser build, Go version, fonts, viewport, page complexity, and concurrency can change the result, so benchmark your own workload.

Method 1: chromedp (the usual default)

chromedp is a high-level Chrome DevTools Protocol client. It can navigate, wait for page state, and capture the current viewport, an entire page, or one element.

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

Install and provide a browser

Add the module with go get github.com/chromedp/chromedp. chromedp does not replace the browser executable; install Chrome or Chromium on the host, or run the headless-shell container image documented by the project. In production, pin the browser build and keep it patched.

Complete example: HTML string to a full-page PNG

package main

import (
    "context"
    "net/url"
    "os"
    "time"

    "github.com/chromedp/chromedp"
)

func main() {
    html := `<!doctype html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>body{font:16px system-ui;margin:32px} .card{padding:24px;background:#eef;border-radius:12px}</style>
</head>
<body><div class="card"><h1>Rendered by Chrome</h1><p>Ready for capture.</p></div></body>
</html>`

    browserCtx, cancel := chromedp.NewContext(context.Background())
    defer cancel()
    ctx, cancel := context.WithTimeout(browserCtx, 30*time.Second)
    defer cancel()

    target := "data:text/html," + url.PathEscape(html)
    var png []byte
    err := chromedp.Run(ctx,
        chromedp.Navigate(target),
        chromedp.WaitVisible("body", chromedp.ByQuery),
        chromedp.FullScreenshot(&png, 100),
    )
    if err != nil {
        panic(err)
    }
    if err := os.WriteFile("out.png", png, 0o644); err != nil {
        panic(err)
    }
}

FullScreenshot with quality 100 emits PNG. A value from 0 through 99 emits JPEG instead, so keep 100 for a lossless PNG. The timeout bounds navigation and capture; choose a value appropriate for your pages.

Viewport, full-page, and element captures

  • chromedp.CaptureScreenshot(&buf) captures the currently visible viewport.
  • chromedp.FullScreenshot(&buf, 100) captures the whole page and emits PNG at quality 100.
  • chromedp.Screenshot(selector, &buf) captures the element matched by a selector.

For a selector capture, replace the full-page action with chromedp.Screenshot(".card", &png). For a URL, replace the data URL with chromedp.Navigate("https://example.com"). If output dimensions matter, set Chrome viewport metrics before navigation and choose a device scale factor; otherwise the browser’s default viewport controls the PNG size.

Wait for the page you actually need

A visible body only proves that initial markup exists. Prefer a deterministic application selector such as [data-render-ready="true"] and use chromedp.WaitVisible or a related wait action. For pages that load web fonts, images, or client-rendered data, have the page set that marker only after those resources are ready. A fixed sleep can be useful as a last resort, but it is less reliable than a state-based wait and may waste time on fast pages.

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

Capturing a remote page

Navigate to the HTTPS URL, wait for an application-specific ready selector, then call the same screenshot action. Set navigation and resource timeouts in your host application. Treat third-party resources as optional: a successful DOM load does not guarantee that every ad, font, analytics request, or image finished before capture.

Method 2: Playwright Go

Playwright provides a clear browser automation API and browser-context isolation. Its documented Go flow starts Playwright, launches Chromium, creates a page, calls SetContent, and saves page.Screenshot.

Complete example

package main

import (
    "log"

    "github.com/mxschmitt/playwright-go"
)

func main() {
    pw, err := playwright.Run()
    if err != nil { log.Fatal(err) }
    defer pw.Stop()

    browser, err := pw.Chromium.Launch()
    if err != nil { log.Fatal(err) }
    defer browser.Close()

    page, err := browser.NewPage()
    if err != nil { log.Fatal(err) }

    html := `<html><body><h1>Hello from Go</h1><p>PNG output</p></body></html>`
    if err := page.SetContent(html); err != nil { log.Fatal(err) }

    if _, err := page.Screenshot(playwright.PageScreenshotOptions{
        Path: playwright.String("html.png"),
        FullPage: playwright.Bool(true),
    }); err != nil { log.Fatal(err) }
}

Install the Go module and the matching Playwright browser binaries according to the release you pin. Keep the module and browser versions together in deployment; upgrading one without the other can change rendering or break launch.

Assets and relative URLs

SetContent is convenient for self-contained HTML. If the markup references relative stylesheets, images, or fonts, give it a resolvable base URL (for example, serve the document from a controlled local HTTP origin) or use absolute URLs. Otherwise the page may render without those assets and still produce a technically valid but incomplete PNG.

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

Method 3: go-webengine when Chromium is not acceptable

go-webengine advertises a pure-Go, CGO_ENABLED=0 headless engine. Its documented API includes a PNG Screenshot helper, local RenderHTML, RenderWithLinks, and an Engine.DisableJS option. The project describes support for a CSS subset, DOM-bound JavaScript, layout (including flexbox, grid, tables, and positioning), images, SVG, gradients, shadows, and dark mode.

The basic shape is:

ctx := context.Background()
rect := image.Rect(0, 0, 1024, 768)
pngBytes, err := engine.Screenshot(ctx, "https://example.com", rect)
if err != nil { return err }
err = os.WriteFile("out.png", pngBytes, 0o644)

Use the module path and exact constructor shown by the version you select. For local markup, use its RenderHTML API; set DisableJS when scripts are unnecessary or undesirable. The documented viewport width is fixed by the render rectangle and the resulting height grows to fit the page, at least the viewport height.

This is a compatibility-led decision, not a drop-in Chrome replacement. Test your exact CSS, font files, SVGs, image formats, and JavaScript. Pages designed around browser-specific APIs, complex third-party widgets, or cutting-edge CSS may require Chromium.

Reliable full-page captures

  1. Choose dimensions first. Set viewport width, height, and device scale factor before loading the page.
  2. Use a ready marker. Wait for a selector that your application sets after data, fonts, and critical images are ready.
  3. Make assets resolvable. Supply a base URL or serve local files from a controlled origin; otherwise relative references fail.
  4. Select the capture scope. Use viewport capture for what a user sees, full-page capture for scrolling documents, or an element selector for cards and reports.
  5. Write bytes atomically. Save to a temporary file and rename it when producing files consumed by another process.
  6. Isolate untrusted input. Use a separate browser context, restrict navigation and network access, and impose timeouts when rendering user-supplied HTML.

Useful rendering controls

Browser APIs expose the controls you generally need for production screenshot services:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Viewport and device scale factor for predictable pixel dimensions.
  • Full-page or element-only capture to avoid stitching screenshots yourself.
  • Navigation, selector, and network-idle waits for asynchronous applications.
  • Custom headers, cookies, user-agent, timezone, and geolocation for authenticated or localized pages.
  • Blocking selected requests or resource types to reduce tracking noise and speed up deterministic renders.
  • Custom CSS or JavaScript to hide animations, consent overlays, or volatile timestamps before capture.

Apply these controls deliberately. Blocking fonts can change line wrapping; disabling JavaScript can remove the content you intended to capture; waiting for network idle can never finish on pages with long polling.

Troubleshooting

Chrome or Chromium will not launch

Check that the executable exists in the container or host, that its architecture matches the Go process, and that the sandbox policy permits launch. In containers, use the documented headless-shell image or the browser flags and shared-memory settings recommended for your environment. Do not disable security isolation globally just to hide a launch error.

The PNG is blank or only contains a skeleton

The capture likely ran before client rendering completed, a required script failed, or the page redirected to a bot-check screen. Wait for an application-ready selector, inspect console and network errors, and verify that the browser can reach every required origin.

Fonts or images are missing

Ensure URLs are absolute or have a valid base origin, wait for the page’s font and image promises, and verify certificates and outbound network policy. A fallback font can change line breaks even when the page otherwise looks correct.

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.

Full-page output is clipped

Use the library’s full-page action rather than a viewport screenshot. Check for fixed-height containers, nested scroll regions, and content that appears only after scrolling; trigger the page’s lazy-loading behavior before capture.

Relative assets disappear with SetContent

SetContent has no useful site origin for relative paths. Serve the HTML through a local HTTP handler or convert references to absolute URLs, then wait for the assets before taking the screenshot.

Captures are slow or memory-heavy

Reuse a browser process while creating isolated contexts or pages, limit concurrent pages, block unneeded resource types, and avoid unnecessarily large device scale factors. Measure startup, navigation, rendering, and encoding separately under your real workload; no generic benchmark establishes a universal winner.

Security and operational safeguards

  • Render untrusted HTML in an isolated context with a restrictive network policy; do not allow it to reach internal services.
  • Set hard navigation and overall job deadlines, and cancel the Go context when a job is abandoned.
  • Limit HTML size, image dimensions, and concurrent jobs to control CPU and memory.
  • Keep Chromium, Playwright, and Go modules patched and pin versions for reproducibility.
  • Record the viewport, browser version, URL, and readiness condition with each job so visual changes are diagnosable.
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. It accepts a URL and returns PNG, JPEG, WebP, or PDF, so your Go service does not need to install or supervise Chrome. Before capture it accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled.

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

Only clean shots are billed. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

One-call request

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

Go developers can make the same HTTP GET with net/http, passing access_key and url as query parameters and writing the response body to a file. The complete API parameters and response details are in the ScreenshotNeo documentation.

Equivalent Python and Node.js calls

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}`);

Controls and pricing

ScreenshotNeo supports full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper size/margins/orientation/page ranges, HTML/CSS-to-image, custom CSS and JavaScript, pre-capture clicks, hidden selectors, waits for selectors/delay/network idle, ad/tracker/request/resource blocking, custom headers/cookies/user-agent/Authorization, timezone and geolocation, transparent backgrounds, image resizing, configurable-TTL caching, signed public image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, an OpenAPI specification, and parameter names used by other screenshot APIs for easier migration.

Plan Included shots Price
Free 1,000 per month $0, no card
Starter 3,000 $5
Growth 15,000 $15
Pro 60,000 $39
Scale 250,000 $99
Business 1,000,000 $249

Yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

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

FAQ

Can I produce PNG without installing Chrome?

Yes, go-webengine is designed for a CGO-free process without Chromium. Verify its rendering compatibility against your pages before relying on it for production output.

Why does a full-page PNG differ from what I see on screen?

A full-page capture lays out content beyond the current viewport and may trigger lazy loading. Fixed elements, nested scroll containers, and content that appears only after interaction can therefore differ from a simple viewport screenshot.

Should I use a data URL for large HTML documents?

Data URLs are convenient for small, self-contained examples. For large documents or many assets, serve the HTML from a controlled local origin so URL length, relative paths, and caching are predictable.

Frequently Asked Questions

Can I produce PNG without installing Chrome?

Yes. go-webengine is designed for a CGO-free process without Chromium, but test your exact CSS, fonts, images, SVG, and JavaScript for compatibility first.

Free tools Windows power users keep installed

One-click scans. No signup required.

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

Why does a full-page PNG differ from what I see on screen?

Full-page capture lays out content beyond the viewport and can trigger lazy loading; fixed elements, nested scroll containers, and interaction-driven content may therefore differ.

Should I use a data URL for large HTML documents?

Use data URLs for small self-contained examples. For larger documents, serve HTML from a controlled local origin so URL length and relative assets remain predictable.

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.