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/bagmeCreate 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:
@pagerules withsizeandmargin - 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.