Capture a Webpage Screenshot in Go with chromedp and Save It to a File
Use chromedp to capture an element, viewport, or full page in Go, then save the returned bytes to a PNG or JPEG file.
To save a webpage screenshot with chromedp, navigate Chromium to the page, run a screenshot action into a []byte, then write those bytes with os.WriteFile. Use chromedp.CaptureScreenshot for the visible viewport, chromedp.FullScreenshot for a full-page image, or chromedp.Screenshot for a selected element.
Set up chromedp
- Install Go and a Chrome or Chromium browser that chromedp can launch.
- Create a Go module and add chromedp:
mkdir chromedp-shot
cd chromedp-shot
go mod init example.com/chromedp-shot
go get github.com/chromedp/chromedp
Chromedp runs Chrome headlessly by default. The browser process is started when you run actions on a new context. See the maintainers’ screenshot example and the chromedp package reference.
Capture a full webpage and save it as PNG
This complete program accepts a URL and optional output path. It captures the page beyond the viewport and writes a PNG. A quality of 100 makes FullScreenshot return PNG data.
package main
import (
"context"
"flag"
"fmt"
"os"
"time"
"github.com/chromedp/chromedp"
)
func main() {
url := flag.String("url", "https://example.com", "URL to capture")
out := flag.String("out", "screenshot.png", "output image path")
flag.Parse()
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
// Bound navigation and capture time so a slow page does not wait forever.
ctx, cancel = context.WithTimeout(ctx, 60*time.Second)
defer cancel()
var image []byte
err := chromedp.Run(ctx,
chromedp.Navigate(*url),
chromedp.FullScreenshot(&image, 100),
)
if err != nil {
fmt.Fprintf(os.Stderr, "capture %q: %v\n", *url, err)
os.Exit(1)
}
if len(image) == 0 {
fmt.Fprintln(os.Stderr, "capture returned no image data")
os.Exit(1)
}
if err := os.WriteFile(*out, image, 0o644); err != nil {
fmt.Fprintf(os.Stderr, "write %q: %v\n", *out, err)
os.Exit(1)
}
fmt.Printf("Saved %s (%d bytes)\n", *out, len(image))
}
Run it with:
go run . -url https://example.com -out page.png
The screenshot action fills the byte slice; saving is a separate filesystem operation. Check both errors. The file mode 0o644 gives the owner read/write access and other users read access, subject to the process umask.
Choose the capture area
| Goal | Action | Notes |
|---|---|---|
| One visible element | chromedp.Screenshot(selector, &image, chromedp.NodeVisible) |
Use a CSS selector such as #report. The node must exist and be visible. |
| Current viewport | chromedp.CaptureScreenshot(&image) |
Captures the current browser viewport, not the entire document. |
| Full page | chromedp.FullScreenshot(&image, 100) |
Captures beyond the viewport. Quality 100 selects PNG. |
Replace the full-page action in the program with one of these actions to change scope. For example, for the current viewport:
err := chromedp.Run(ctx,
chromedp.Navigate(*url),
chromedp.CaptureScreenshot(&image),
)
For an element, wait for a stable selector before capturing it:
selector := "main article"
err := chromedp.Run(ctx,
chromedp.Navigate(*url),
chromedp.WaitVisible(selector),
chromedp.Screenshot(selector, &image, chromedp.NodeVisible),
)
A target site can change its markup, so treat selectors as site-specific and verify that the chosen node is the one you intend to save.
PNG and JPEG output
FullScreenshot takes a quality value from 0 through 100. At 100 it produces PNG; other values produce JPEG. Give the file an extension that matches the bytes:
// PNG
chromedp.FullScreenshot(&image, 100)
err = os.WriteFile("page.png", image, 0o644)
// JPEG
chromedp.FullScreenshot(&image, 90)
err = os.WriteFile("page.jpg", image, 0o644)
Do not use a .png extension with a non-100 quality setting. The extension does not convert the image format; it only names the file.
Wait for page content before capturing
Navigate waits for navigation according to browser behavior, but pages that render content asynchronously may need an additional condition. Wait for a selector that signals the content is ready, or add a deliberate delay when the page has no reliable readiness marker.
err := chromedp.Run(ctx,
chromedp.Navigate(*url),
chromedp.WaitVisible("main .report", chromedp.ByQuery),
chromedp.FullScreenshot(&image, 100),
)
For a fixed delay, add chromedp.Sleep(2 * time.Second) before the screenshot action. A selector wait is generally more reliable than guessing a delay. Increase the context timeout to cover navigation plus any additional wait.
Device emulation and full-page captures
If your workflow configures device emulation or a viewport, account for the behavior documented by the screenshot example: FullScreenshot overrides device emulation settings. Reset those settings with device.Reset before continuing with work that depends on the emulated viewport. Use viewport capture when you need to preserve the configured viewport for the screenshot itself.
Or skip the browser setup
ScreenshotNeo provides a screenshot API: one GET request takes a URL and returns an image or PDF. This example saves the response as WebP; see the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
Cookie banners, newsletter popups, and chat widgets are removed before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers tell you the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000.
Sign up for 1,000 free screenshots a month, with no card.
Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Chrome does not launch | Chrome or Chromium is missing, or the environment cannot start a browser process. | Install a compatible browser and check that the process has permission to launch it. In containers, configure the browser environment appropriately. |
context deadline exceeded |
Navigation, page rendering, selector wait, or screenshot exceeded the context deadline. | Check the URL and network access, wait for a specific ready selector, and set a timeout appropriate to the page. |
context canceled |
The context was canceled, possibly because the browser connection was lost or a parent context was canceled. | Keep the context alive through chromedp.Run; inspect browser startup and connection errors before retrying. |
| Element screenshot fails or is empty | The selector does not match, or the node is not visible. | Check the selector against the current page, wait for it to become visible, and use the appropriate query strategy. |
| Image file is missing | The capture failed before writing, or the output directory is unavailable. | Handle the chromedp.Run and os.WriteFile errors separately; use an existing writable directory. |
| JPEG content has a PNG name | A non-100 quality value was used with FullScreenshot. |
Use quality 100 for PNG, or save non-100 output with a .jpg or .jpeg extension. |
| Full-page result has unexpected dimensions | Full-page capture overrides device emulation settings. | Use viewport capture when emulation must remain in effect, or reset emulation with device.Reset after full capture. |
Performance, reliability, and cost
- Reuse a browser for batches. Starting a browser for every URL adds startup overhead. For repeated captures, use chromedp’s browser and context lifecycle deliberately, while isolating per-page timeouts and handling failures individually.
- Keep page readiness specific. Waiting for a meaningful selector avoids both premature captures and unnecessarily long fixed sleeps.
- Bound work. Set a context deadline, limit concurrent browser work to the capacity of the host, and write to a known writable path. Large full-page images use more memory and disk than viewport captures.
- Expect external pages to change. Network conditions, scripts, consent flows, and changing page markup can alter results. Validate selectors and the resulting image when the target changes.
- Plan for infrastructure costs. chromedp is the Go automation library, but your application still needs a machine with a compatible browser and enough resources for its capture workload. The cited sources provide no benchmark or fixed cost figure.
FAQ
Does chromedp save the screenshot file itself?
No. Screenshot actions return image bytes through the buffer you provide. Use Go file I/O, such as os.WriteFile, to save them.
Can I capture only the part of a page I need?
Yes. Use chromedp.Screenshot with a selector for a visible element, or capture the viewport or full page depending on the output you need.
Why does the output look different from my normal browser?
Chromedp uses headless Chrome by default, and page behavior can depend on viewport and emulation settings. Also, full-page capture overrides device emulation settings; check those settings when comparing results.
Which chromedp version should I use?
Use the version pinned by your Go module and check its package documentation for the APIs available in that version. The examples and implementation can evolve.


