How to Convert a Webpage to PDF in Go with Chromium
Use chromedp to open a webpage in Chrome or Chromium, print it to PDF, and save the result. Includes print settings, dynamic page readiness, and troubleshooting.
Use chromedp to control Chrome or Chromium from Go, navigate to a URL, call Chrome DevTools Protocol’s Page.printToPDF method, and write the returned bytes to a file. The browser is a separate dependency: chromedp is the Go automation layer, and it does not bundle Chrome or Chromium.
Prerequisites and setup
- Install a current Go toolchain and make Chrome or Chromium available to the process, or connect to a compatible remote DevTools endpoint. The chromedp project documents local, container, and remote-browser setups.
- Create a module and add chromedp:
mkdir webpage-pdf cd webpage-pdf go mod init example.com/webpage-pdf go get github.com/chromedp/chromedp - Save the program below as
main.go. It accepts a URL, output path, and optional CSS selector to wait for before printing.
Complete Go example
package main
import (
"context"
"flag"
"fmt"
"os"
"time"
"github.com/chromedp/cdproto/page"
"github.com/chromedp/chromedp"
)
func main() {
url := flag.String("url", "https://example.com", "webpage URL to print")
out := flag.String("out", "page.pdf", "output PDF path")
waitSelector := flag.String("wait-selector", "", "optional CSS selector to wait for")
flag.Parse()
if *url == "" {
fatalf("-url must not be empty")
}
// Bound browser startup, navigation, readiness, and PDF generation.
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()
// NewContext prepares a chromedp context. Chrome is started on the first Run.
browserCtx, cancelBrowser := chromedp.NewContext(ctx)
defer cancelBrowser()
actions := []chromedp.Action{
chromedp.Navigate(*url),
}
if *waitSelector != "" {
actions = append(actions, chromedp.WaitVisible(*waitSelector, chromedp.ByQuery))
}
var pdf []byte
actions = append(actions, chromedp.ActionFunc(func(ctx context.Context) error {
var err error
pdf, _, err = page.PrintToPDF().
WithPrintBackground(true).
WithPreferCSSPageSize(true).
Do(ctx)
return err
}))
if err := chromedp.Run(browserCtx, actions...); err != nil {
fatalf("render webpage: %v", err)
}
if len(pdf) == 0 {
fatalf("Chrome returned an empty PDF")
}
if err := os.WriteFile(*out, pdf, 0o644); err != nil {
fatalf("write PDF: %v", err)
}
fmt.Printf("Wrote %s (%d bytes)\n", *out, len(pdf))
}
func fatalf(format string, args ...any) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
os.Exit(1)
}
Run it with:
go run . -url https://example.com -out example.pdf
go run . -url https://example.com -wait-selector "main article" -out article.pdf
The example enables print backgrounds and lets the page’s CSS define paper sizing when it has print page rules. Remove or change those settings if you need fixed paper dimensions or do not want background graphics. Check the generated API for the version of cdproto installed in your module; its available setters follow the Chrome DevTools Protocol and can change as the protocol evolves. See the cdproto page package reference and the chromedp PDF example.
How the conversion works
context.WithTimeoutbounds the overall operation. Cancellation stops work that exceeds the deadline.chromedp.NewContextestablishes browser automation state. It does not launch Chrome immediately; the firstchromedp.Rundoes.chromedp.Navigateloads the URL. Navigation completion alone may not mean a client-rendered page, its fonts, or asynchronous data are ready.- An optional
chromedp.WaitVisiblewaits for a meaningful element. Choose a selector that appears only when the content needed in the PDF is ready. page.PrintToPDF().Do(ctx)asks Chrome to print the current page and returns PDF bytes.os.WriteFilewrites those bytes. Errors from rendering and file output are reported separately.
Choosing print settings
Page.printToPDF exposes settings for page layout and PDF generation. The exact Go method names are generated from the protocol; inspect the installed cdproto version if a setter differs.
| Setting | When to use it |
|---|---|
| Landscape | Use landscape for wide tables, dashboards, or diagrams. Portrait is the default orientation. |
| Paper width and height | Set dimensions when the output must use a known paper size. If not supplied, Chrome uses its print defaults. |
| Margins | Set top, bottom, left, and right margins deliberately when content must fit a particular printable area. |
| Prefer CSS page size | Enable when the page’s print CSS declares page dimensions. It defaults to false; otherwise content is scaled to fit the paper dimensions. |
| Print background | Enable to retain background colors and images. Background printing is off by default. |
| Display header and footer | Enable when each printed page should include browser-generated headers or footers. This is off by default. |
| Header/footer templates | Supply HTML templates when enabled. Supported classes can insert date, title, URL, page number, and total pages. |
| Scale | Adjust the printed content scale if the layout needs to fit differently. |
| Page ranges | Print selected pages when the whole document is not needed. |
| Tagged PDF and document outline | Set these options when accessibility structure or a navigable outline is required and supported by the browser version. |
Defaults documented by the generated binding include portrait orientation, headers and footers off, backgrounds off, and preferCSSPageSize false. For stable output, choose paper dimensions, orientation, and margins intentionally instead of relying on defaults.
Dynamic pages and readiness
Sites often render content after the initial document navigation, such as client-side application data or lazy-loaded content. There is no single readiness signal that fits every site. Prefer waiting for the element or application state that indicates the content you need is present. A fixed sleep can be used as a last resort, but it may waste time on fast pages and still be too short on slow ones.
If a selector is not visible until the page is scrolled, or content loads only as it enters the viewport, the simple example may print before that content appears. Adapt the browser actions to the site: scroll or trigger the relevant interaction, then wait for a stable selector before calling PrintToPDF. Inspect the resulting PDF, since navigation success alone does not guarantee every image, font, or asynchronous request is complete.
Deployment choices
- Local Chrome or Chromium: straightforward for development and deployments where the browser binary is installed and maintained alongside the app.
- Headless container: a container such as
chromedp/headless-shellis an option for containerized infrastructure. It is not a requirement; the host must still provide a compatible browser process. - Remote DevTools endpoint: connect to an already managed Chrome/Chromium instance when browser lifecycle is handled separately. Account for endpoint availability and network access in your application.
Choose based on where the browser runs, who maintains its version and lifecycle, and how browser processes are isolated. The referenced project documentation does not establish comparative performance or cost figures for these deployment options.
Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Chrome executable not found or browser startup fails | No compatible Chrome/Chromium binary is installed, or it is not available to the process. | Install a browser or use a headless container/remote DevTools target. Confirm the runtime environment can launch or reach it. |
| Navigation or printing hits the deadline | The page, browser startup, or an application-specific wait did not finish within the context timeout. | Check browser reachability and the URL. Increase the deadline only if the workload needs it; use a meaningful readiness condition rather than an arbitrary long sleep. |
| PDF is missing content or shows a loading state | Navigation ended before client-side rendering or required data finished. | Wait for a page-specific selector or state that represents completed content, then inspect the generated PDF. |
| Background colors or images are absent | Chrome’s print background option defaults off. | Set WithPrintBackground(true) or the equivalent setter in the installed binding. |
| Paper size or scaling looks wrong | CSS page sizing, paper dimensions, and scaling interact; CSS page sizing defaults off. | Decide whether CSS or explicit paper dimensions should control the page size. Set orientation and margins explicitly and review print CSS. |
| Header/footer text is missing | Display headers and footers is off by default, or the template is not configured as expected. | Enable the option and check the template and supported placeholder classes. |
| PDF generation succeeds but file writing fails | The output directory is missing, not writable, or the path is invalid. | Create an accessible destination directory and check the returned os.WriteFile error. |
| Unknown setter or compile error in print configuration | The generated cdproto API differs from the version the code was written against. | Inspect the installed github.com/chromedp/cdproto/page documentation and use the setters generated for that version. |
Performance, reliability, and cost
Each conversion requires browser work and a page load, so total time depends on browser startup, the target site, and its rendering behavior. The available project sources do not provide universal throughput, memory, fidelity, or compatibility guarantees. Reuse a browser with child tab contexts when the application architecture calls for it, and bound each job with a context deadline. Ensure cancellation and errors are propagated so failed navigation or PDF generation is not mistaken for a successful file.
Chrome/Chromium deployment has operational costs such as supplying and maintaining a browser process and the compute required by the pages you render. No general per-page cost can be stated from the cited sources; measure your own workloads and hosting arrangement.
Or skip the browser setup
If the goal is to capture a page without installing or operating Chromium, ScreenshotNeo is a website screenshot API and MCP server. Its API also supports PDF output; see the API documentation for PDF options. This one-call example captures a WebP screenshot of the same page:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
ScreenshotNeo accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the capture; those steps can be turned off. Bot checks, blank pages, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card.
FAQ
Does chromedp include Chrome?
No. Install Chrome/Chromium separately or connect to a compatible DevTools target.
Can I print only a page range?
Yes. The protocol supports page ranges; check the generated binding for the installed cdproto version.
Why does the PDF differ from the webpage on screen?
Chrome uses print layout rules and print settings. Review the page’s print CSS, paper sizing, margins, background setting, and readiness condition.
Can I create PDFs without managing Chromium?
Yes. ScreenshotNeo provides PDF capture through its API and MCP server; its documentation describes the available options.


