Hi there! I'm Shrijith Venkatrama, founder of Hexmos. Right now, I’m building LiveAPI, a tool that makes generating API docs from your code ridiculously easy.
PDF generation isn't a one-size-fits-all problem.
Depending on whether you're building simple invoices, complex reports, beautiful exports, or dynamic UIs turned into PDFs, there are several solid approaches you can use in Go.
This article covers the main practical methods with complete examples, pros/cons, and guidance on when to pick what.
Why Generate PDFs in Go?
- Full backend control — no SaaS dependency.
- Low latency — avoid extra API hops.
- Customization — adjust layouts, formats, and security options.
- Automation — dynamically build content from your services.
Advantage | Reason |
---|---|
Privacy | No third-party data leaks |
Cost savings | No per-document fees |
Speed | Generate on demand |
Native Go Libraries for PDF Generation
1. Using gofpdf
for Basic PDFs
gofpdf
is the classic choice: pure Go, dependency-free.
Example:
package main
import (
"github.com/jung-kurt/gofpdf"
)
func main() {
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.AddPage()
pdf.SetFont("Arial", "B", 16)
pdf.Cell(40, 10, "Hello, gofpdf!")
pdf.OutputFileAndClose("basic_gofpdf.pdf")
}
Output: A simple one-line "Hello" PDF.
Pros | Cons |
---|---|
Pure Go | Manual layout effort |
No external tools needed | Limited complex layout support |
2. Using unidoc/unipdf
for High-End PDFs
unipdf
is a commercial-grade library.
It's powerful, feature-rich, and paid for production — but free for personal and trial use.
Example:
package main
import (
"github.com/unidoc/unipdf/v3/model"
"log"
"os"
)
func main() {
pdf := model.NewPdfWriter()
f, err := os.Create("unipdf_output.pdf")
if err != nil {
log.Fatal(err)
}
defer f.Close()
err = pdf.Write(f)
if err != nil {
log.Fatal(err)
}
}
Pros:
- Vector graphics, annotations, encryption.
- Table/grid helpers.
- Commercial support.
Cons:
- License required for production.
Link: UniPDF Documentation
3. Using pdfcpu
for PDF Manipulation
pdfcpu
is more focused on editing, processing, and validating PDFs, but it can generate simple PDFs too.
Example:
package main
import (
"github.com/pdfcpu/pdfcpu/pkg/api"
"log"
)
func main() {
err := api.CreateDemoPDF("pdfcpu_demo.pdf", "", nil)
if err != nil {
log.Fatal(err)
}
}
Pros:
- Splitting, merging, watermarking PDFs.
- CLI and library mode.
Cons:
- Not designed for full creative layout generation.
Link: pdfcpu Documentation
Template-Based Generation: Go → LaTeX → PDF
For professional typesetting, LaTeX is still king.
Generate .tex
files dynamically in Go, compile using pdflatex
.
Example:
package main
import (
"os"
"os/exec"
)
func main() {
content := `\documentclass{article}
\begin{document}
Hello, \textbf{LaTeX} World!
\end{document}`
os.WriteFile("output.tex", []byte(content), 0644)
cmd := exec.Command("pdflatex", "output.tex")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
panic(err)
}
}
Pros | Cons |
---|---|
Stunning typography | Requires LaTeX installed |
Advanced features (TOC, BibTeX) | Learning curve |
Link: Quick Start with LaTeX
Markdown + Pandoc: Flexible and Clean
If you like Markdown, you can generate it dynamically and pipe it through Pandoc to create PDFs.
Example:
package main
import (
"os"
"os/exec"
)
func main() {
markdown := `# Auto Report
This report was generated by Go and Pandoc.`
os.WriteFile("report.md", []byte(markdown), 0644)
cmd := exec.Command("pandoc", "report.md", "-o", "report.pdf")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
panic(err)
}
}
Pros | Cons |
---|---|
Easy content authoring | Needs Pandoc installed |
Good support for tables, links, images | Slightly heavier pipeline |
Link: Pandoc Markdown Syntax
HTML to PDF: Chrome Headless, wkhtmltopdf
If you already have HTML layouts (e.g., templates, dashboards), rendering HTML into PDF makes perfect sense.
Using Headless Chrome (via chromedp
)
chromedp
lets you programmatically control Chrome.
Example:
package main
import (
"context"
"io/ioutil"
"log"
"time"
"github.com/chromedp/chromedp"
)
func main() {
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
var pdfBuf []byte
err := chromedp.Run(ctx,
chromedp.Navigate("https://example.com"),
chromedp.Sleep(2*time.Second), // wait for page load
chromedp.ActionFunc(func(ctx context.Context) error {
var err error
pdfBuf, _, err = page.PrintToPDF().Do(ctx)
return err
}),
)
if err != nil {
log.Fatal(err)
}
ioutil.WriteFile("chrome_output.pdf", pdfBuf, 0644)
}
Pros | Cons |
---|---|
Full CSS, JS, dynamic pages | Heavy runtime (Chrome) |
Link: chromedp PrintToPDF
Using wkhtmltopdf
wkhtmltopdf
is a simpler, mature tool: turn any HTML file into a PDF without needing full Chrome.
Example:
package main
import (
"os/exec"
"log"
)
func main() {
cmd := exec.Command("wkhtmltopdf", "https://example.com", "output.pdf")
cmd.Stdout = nil
cmd.Stderr = nil
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
}
Pros | Cons |
---|---|
Lightweight | Not always perfect with complex JS |
Link: wkhtmltopdf Manual
Quick Comparison of All Methods
Method | Best For | Needs External Tools? | Difficulty |
---|---|---|---|
gofpdf |
Simple static PDFs | No | Easy |
unipdf |
Complex dynamic documents | No (license needed) | Medium |
pdfcpu |
PDF processing and editing | No | Easy |
Go → LaTeX → PDF | High-quality, complex typesetting | Yes (LaTeX) | Medium-Hard |
Go → Markdown → Pandoc → PDF | Lightweight reports, docs | Yes (Pandoc) | Medium |
Headless Chrome | Full web page snapshots | Yes (Chrome) | Medium |
wkhtmltopdf | Simple HTML to PDF exports | Yes | Easy-Medium |
Choosing the Right Method
Situation | Recommended Option |
---|---|
Static invoices, simple docs | gofpdf |
Long reports, academic documents | LaTeX + pdflatex |
Blog-like or documentation exports | Markdown + Pandoc |
HTML dashboard snapshots | chromedp or wkhtmltopdf |
Need to manipulate existing PDFs | pdfcpu |
Premium enterprise-grade needs | unipdf |
Final Thoughts
The right choice depends heavily on your:
- Required layout complexity
- Tolerance for installing external tools
- Performance and runtime footprint
When in doubt, start simple (like gofpdf
) and scale complexity only when necessary.