# HTML to PDF in Go (Golang) HTML to PDF in Go with chromedp, wkhtmltopdf wrappers, self-hosted Gotenberg, or a plain net/http API call. Runnable code for each. **Quick answer.** Post JSON to `https://html-pdf.com/v1/pdf` with a Bearer key and write the response body to a file. This works from Go's standard library or any shell. ```bash curl https://html-pdf.com/v1/pdf -H "Authorization: Bearer $HTML_PDF_KEY" \ -d '{"html": "
$120.00
` ctx, cancel := chromedp.NewContext(context.Background()) defer cancel() // Start the browser on a blank page, then replace its content. if err := chromedp.Do(ctx, chromedp.Navigate("about:blank")); err != nil { log.Fatal(err) } tree, err := chromedp.Call(ctx, page.GetFrameTree, cdp.Empty{}) if err != nil { log.Fatal(err) } if _, err := chromedp.Call(ctx, page.SetDocumentContent, page.SetDocumentContentParams{ FrameID: tree.FrameTree.Frame.ID, HTML: html, }); err != nil { log.Fatal(err) } printBackground := true res, err := chromedp.Call(ctx, page.PrintToPDF, page.PrintToPDFParams{ PrintBackground: &printBackground, PaperWidth: 8.27, // A4, in inches PaperHeight: 11.69, }) if err != nil { log.Fatal(err) } os.WriteFile("invoice.pdf", res.Data, 0o644) } ``` This uses the generic API that chromedp adopted in v0.17 (tested with v0.20). Older tutorials pass `chromedp.ActionFunc` steps to `chromedp.Run`, which no longer compiles on current versions. Output matches Chrome's print preview, and paper sizes and margins are in inches. The cost is operations. Chrome adds a few hundred MB to your image, and each render needs a browser tab and its memory. Long-running services also need to reuse a browser, cap concurrency and recover from crashed processes. ## Option 2: wkhtmltopdf wrappers This option is only worth it for legacy templates. `github.com/SebastiaanKlippert/go-wkhtmltopdf` wraps the wkhtmltopdf command line with typed options, and it still works. But wkhtmltopdf itself is archived and no longer receives fixes, and its QtWebKit engine predates flexbox and grid support. The wrapper's own README recommends looking at alternatives for new projects. Our [wkhtmltopdf alternative](https://html-pdf.com/wkhtmltopdf-alternative) guide covers migration. ## Option 3: self-hosted Gotenberg Gotenberg gives you Chromium output without Chrome inside your Go service. It packages Chromium and LibreOffice behind an HTTP API in one Docker image. Your Go code just posts a multipart form, which you can do with the standard library instead of a client package. ```bash docker run --rm -p 3000:3000 gotenberg/gotenberg:8 ``` ```go var body bytes.Buffer w := multipart.NewWriter(&body) part, _ := w.CreateFormFile("files", "index.html") // must be named index.html part.Write([]byte("Total: $120.00
`) if err != nil { panic(err) } os.WriteFile("invoice.pdf", pdf, 0o644) } ``` Margins take CSS units (`"15mm"`, `"0.5in"`) instead of inches as floats. To convert a public page, send `"url": "https://example.com"` instead of `html`. A `402` with code `insufficient_credits` means you need to top up. ## Serve the PDF from an HTTP handler Here is how to return the PDF directly from a `net/http` handler. ```go http.HandleFunc("/invoice.pdf", func(w http.ResponseWriter, r *http.Request) { pdf, err := RenderPDF(buildInvoiceHTML(r.URL.Query().Get("id"))) if err != nil { http.Error(w, "could not render PDF", http.StatusBadGateway) return } w.Header().Set("Content-Type", "application/pdf") w.Header().Set("Content-Disposition", `inline; filename="invoice.pdf"`) w.Write(pdf) }) ``` When you build the HTML from user data, escape it with `html.EscapeString` or the `html/template` package. Reference images and fonts by absolute `https://` URLs or inline them, since the renderer can't reach `localhost` or private networks. You can also send `index.html` plus asset files as multipart, with your options in an `options` JSON field. ## Pricing Pricing is per page, with no subscription. 1 credit = 1 PDF page, and you start with 100 free credits. After that, packs go from $10 for 1,000 pages down to $0.005 per page at $250 for 50,000. Purchased credits never expire and failed renders are free. - **Limits:** 20 MB request, 30 seconds per render, 120 requests per minute. More: [page breaks](https://html-pdf.com/html-to-pdf-page-breaks) · [headers, footers and page numbers](https://html-pdf.com/pdf-headers-footers-page-numbers) · [API docs](https://html-pdf.com/docs) · [sign in](https://html-pdf.com/login) ## FAQ ### How do I convert HTML to PDF in Go? Use chromedp to drive headless Chrome, post a form to a self-hosted Gotenberg container, or send JSON to `https://html-pdf.com/v1/pdf` with `net/http` and save the response bytes. ### Is there a pure Go HTML to PDF library? No. Go has no native HTML layout engine, so every reliable option drives Chromium (or the archived wkhtmltopdf binary) somewhere. ### Is wkhtmltopdf still a good choice in Go? Not for new projects. wkhtmltopdf is archived and its QtWebKit engine predates flexbox and grid, so keep it only for legacy templates. ### Do credits expire? No. Purchased credits never expire, 1 credit is 1 PDF page, and failed renders cost nothing.