bagme

bagme — HTML/CSS to PDF in Go

bagme is the quickest way to generate PDF documents from Go code. You write HTML and CSS, and bagme turns it into a typeset PDF — with proper line breaking, hyphenation, and optional accessibility tagging. No need to deal with nodes, boxes, or glue directly.

If you need full control over every typesetting detail, see the Go API documentation. bagme is built on top of the same engine, but hides the complexity behind an HTML/CSS interface.

Getting started

You need Go version 1.24 or later.

Start with an empty directory and a new Go module:

go mod init bagtest
go get github.com/boxesandglue/bagme

Create a file main.go:

package main

import (
	"log"

	"github.com/boxesandglue/bagme/document"
)

func dothings() error {
	d, err := document.New("result.pdf")
	if err != nil {
		return err
	}

	d.Title = "Hello bagme"
	d.Language = "en"

	if err = d.AddCSS(`
		@page { size: a4; margin: 2cm; }
		body { font-family: serif; font-size: 12pt; }
		h1 { color: darkslateblue; }
	`); err != nil {
		return err
	}

	return d.RenderPages(`
		<h1>Hello, World!</h1>
		<p>This PDF was created with bagme.</p>
	`)
}

func main() {
	if err := dothings(); err != nil {
		log.Fatal(err)
	}
}

Run go mod tidy and then go run main.go. The result is a file called result.pdf.

Core concepts

bagme has two modes for placing content on pages:

RenderPages — automatic page breaks

RenderPages takes an HTML string and distributes the content across pages automatically. Page size and margins come from CSS @page rules. This is the mode you’ll use most often.

d.RenderPages(`<h1>Title</h1><p>Content flows across pages...</p>`)

Forced page breaks work with page-break-before: always in CSS.

OutputAt — manual positioning

OutputAt places an HTML snippet at exact coordinates on the current page. Coordinates are measured from the bottom-left corner. Use this for forms, labels, or layouts that need precise placement.

d.OutputAt(`<p>Hello</p>`, width, x, y)

Call NewPage() to start additional pages in this mode.

PDF formats

By default, bagme creates standard PDF files. Pass options to New for other formats:

// Standard PDF
d, err := document.New("output.pdf")

// Accessible PDF (PDF/UA) — automatic structure tagging
d, err := document.New("output.pdf", document.WithPDFUA())

// PDF/A-3b — for archiving
d, err := document.New("output.pdf", document.WithPDFA3b())

PDF/UA automatically tags all HTML elements with their corresponding PDF structure roles (h1→H1, p→P, li→LI, table→Table, etc.) so that screen readers and assistive technologies can interpret the content.

When creating PDF/UA documents, always set Title and Language:

d.Title = "Document title"
d.Language = "en"

ZUGFeRD / Factur-X

WithZUGFeRD creates a ZUGFeRD/Factur-X compliant e-invoice PDF in one call. It sets the format to PDF/A-3b, attaches the XML invoice data, loads an sRGB color profile, and adds the required XMP extension schema metadata:

xmlData, _ := os.ReadFile("factur-x.xml")

d, err := document.New("invoice.pdf",
    document.WithZUGFeRD(xmlData, "EN16931"))

The profile parameter specifies the Factur-X conformance level: "MINIMUM", "BASIC WL", "BASIC", "EN 16931" (or "EN16931"), "EXTENDED", "XRECHNUNG".

Attachments

Embed files in the PDF document:

d, err := document.New("output.pdf", document.WithPDFA3b(),
    document.WithAttachment(document.Attachment{
        Name:        "data.xml",
        Description: "Structured data",
        MimeType:    "text/xml",
        Data:        xmlBytes,
    }))

You can also attach files after creation:

d.AttachFile(document.Attachment{...})

Document metadata

Set metadata on the document before calling Finish:

d.Title    = "The Frog King"
d.Author   = "Brothers Grimm"
d.Subject  = "A classic fairy tale"
d.Keywords = "fairy tale, frog, princess"
d.Creator  = "my application"
d.Language = "en"

CSS support

bagme supports a subset of CSS for styling:

  • Page layout: @page rules with size and margin
  • Fonts: font-family, font-size, font-weight, font-style
  • Text: color, text-align, text-indent, line-height
  • Box model: margin, padding, border, border-radius, background-color
  • Lists: list-style-type

CSS can be added inline (AddCSS) or from files (ReadCSSFile).

Examples

Ready-to-run examples are available in the bagme-examples repository.