I recently had to work on a Go project where PDFs had to be generated, with custom data, based on a template.

After considering a few options, I decided to go with wkhtmltopdf's wrapper github.com/SebastiaanKlippert/go-wkhtmltopdf, since this a solid and widely used open source library and I would only be using a Go layer on top of it.

Also, this would allow the template to be maintained in HTML, which would hopefully end up in less headaches than creating PDF's with a lower level generator kind of thing.

Cut to the chase

Not directly related... But I defined a interface for this service so it could be mocked, and so on.

package domain

// PDFService represents the interface of a pdf generation service
type PDFService interface {
	GeneratePDF(data *SomeModel) ([]byte, error)

Here's the service itself. It quite self explanatory but still, I've added comments on meaningful steps.

It essentially consists of first generating an HTML file based on a template and then pass that HTML to wkhtmltopdf.

package pdf

import (
	domain "the-project"

type PDFService struct {}

func NewPDFService() *PDFService {
	return &PDFService{}

func (p PDFService) GeneratePDF(data *domain.SomeModel) ([]byte, error) {
	var templ *template.Template
	var err error

	// use Go's default HTML template generation tools to generate your HTML
	if templ, err = template.ParseFiles("pdf-template.html"); err != nil {
		return nil, err

	// apply the parsed HTML template data and keep the result in a Buffer
	var body bytes.Buffer
	if err = templ.Execute(&body, data); err != nil {
		return nil, err

	// initalize a wkhtmltopdf generator
	pdfg, err := wkhtmltopdf.NewPDFGenerator()
	if err != nil {
		return nil, err

	// read the HTML page as a PDF page
	page := wkhtmltopdf.NewPageReader(bytes.NewReader(body.Bytes()))

	// enable this if the HTML file contains local references such as images, CSS, etc.

	// add the page to your generator

	// manipulate page attributes as needed

	// magic
	err = pdfg.Create()
	if err != nil {
		return nil, err

	return pdfg.Bytes(), nil


Say you want to serve the PDF in an API response:

func (h *YourHandler) GetPDF(w http.ResponseWriter, r *http.Request) {
	// .....

	pdfBytes, err := h.pdfService.GeneratePDF(&data)
	if err != nil {
		httputil.RespondInternalError(w, r, err)

	w.Header().Set("Content-Disposition", "attachment; filename=kittens.pdf")
	w.Header().Set("Content-Type", "application/pdf")