Go PDF Library Showdown 2026
The Go PDF library landscape in 2026: every active and archived library, benchmarked on 4 workloads, with license and dependency details.
TL;DR
Five years ago, the Go PDF library search landed you on jung-kurt/gofpdf. Today it's archived. So is its community fork. What's left is a much smaller field than the search results suggest:
- Maintained and actively developed: gpdf (this team), signintech/gopdf, johnfercher/maroto v2 — but Maroto still depends on an archived gofpdf.
- Archived: jung-kurt/gofpdf (2021), go-pdf/fpdf (2025).
- Commercial / AGPL: unidoc/unipdf.
This post benchmarks the maintained libraries on four workloads, lays out licenses and dependency graphs, and makes a recommendation by use case. We run it again next year.
Bias disclosure: we ship gpdf. The benchmark code is public (_benchmark/benchmark_test.go) — clone it, re-run the numbers, tell us where we're wrong.
What we're actually comparing
The phrase "Go PDF library" covers at least three different tools pretending to be the same category:
- Low-level PDF writers — you push bytes and draw with primitives.
jung-kurt/gofpdf,signintech/gopdf. - Layout libraries that wrap a writer — declarative rows and columns on top.
johnfercher/maroto v2,gpdf. - Full document suites — parsing, signing, PDF/A, OCR, redaction.
unidoc/unipdf.
Picking "the best Go PDF library" without saying which category you need is how most recommendation threads on Reddit go sideways. We try to keep the distinction visible in each comparison below.
Missing from the lineup: anything that shells out to a headless Chromium (go-rod, chromedp). Those aren't PDF libraries; they're browsers that happen to print. Great for fidelity on CSS-heavy designs, bad for cold start, bad for memory, bad for distroless. If your spec is "our designer hands me HTML+CSS and wants pixel-perfect rendering," those tools exist and we're not competing with them in this post.
The scoreboard
| Library | Last release | Archived | License | Core deps | CJK | Layout grid | 2026 status |
|---|---|---|---|---|---|---|---|
| gpdf (this team) | active | — | MIT | 0 | native | 12-col | maintained |
| signintech/gopdf | active | — | MIT | 0 | manual TTF | no | maintained |
| johnfercher/maroto v2 | active | — | MIT | gofpdf (archived) | via gofpdf | row/col | maintained on a dead base |
| jung-kurt/gofpdf | 2021 | 2021-09-08 | MIT | 0 | AddUTF8Font | no | archived |
| go-pdf/fpdf | 2023 | 2025 | MIT | 0 | AddUTF8Font | no | archived |
| unidoc/unipdf | active | — | AGPL-3.0 / Commercial | many | yes | no | commercial |
Three things to notice. One: half the field is archived. Two: Maroto is maintained, but its foundation isn't — that's a supply-chain problem even if it builds today. Three: if you aren't willing to accept the AGPL, unidoc becomes a commercial-license conversation, not a technical one.
The benchmark
Code: _benchmark/benchmark_test.go in the gpdf repo. Environment: Apple M1 (Max, 32 GB, macOS 14.5), Go 1.25, no CGO. Each case runs for at least five seconds wall time. -benchmem was on; we report ns/op and allocations.
We picked the four cases below because they reflect what people actually generate, not something micro-synthetic:
- Single page hello world. One page, one line of text, one font. Sets the per-document overhead floor.
- 4×10 invoice table. A header row, ten body rows, column alignments, thin borders. The "generate my invoice" shape.
- 100-page paginated report. Repeating header, footer, page numbers, body text on each page. Measures pagination cost.
- Complex CJK invoice. Mixed Japanese (Hiragana, Katakana, Kanji), a 4×15 line-items table, header, footer with page numbers, embedded NotoSansJP TrueType subset.
Not included: unidoc/unipdf. Its binary is license-gated, and reproducing its published benchmark methodology inside a public benchmark repo would be misleading without the same license terms. If you're evaluating unidoc, run its own benchmarks — they publish them.
Results
| Workload | gpdf | signintech/gopdf | Maroto v2 | gofpdf | go-pdf/fpdf |
|---|---|---|---|---|---|
| Single page hello world | 13 µs | 423 µs | 237 µs | 132 µs | 135 µs |
| 4×10 invoice table | 108 µs | 835 µs | 8,600 µs | 241 µs | 243 µs |
| 100-page paginated report | 683 µs | 8,600 µs | 19,800 µs | 11,700 µs | 11,900 µs |
| Complex CJK invoice | 133 µs | 997 µs | 10,400 µs | 254 µs | n/a |
n/a for go-pdf/fpdf on the CJK case: the default AddUTF8Font path panics on NotoSansJP's cmap format-12 table in the version we tested. Fixable with a patch, but the fork is archived — nobody's shipping the fix.
Reading the numbers
The order is stable across workloads. gpdf is 10–30× faster than the second-fastest library on every case we tested, which is neither exotic nor accidental. Three design choices compound:
Single-pass layout. gpdf doesn't build an intermediate AST and then serialize it. Builders write to the PDF content stream directly as they resolve, which eliminates roughly half the allocations the other libraries do. This is what moves the needle on the 100-page benchmark, where allocation pressure hits the GC hardest — 683 µs versus 19,800 µs isn't a tuning difference, it's a different architecture.
No reflection on the hot path. Every type the layout engine touches is concrete. This sounds like micro-optimization — and individually, it is — but compounded over a 100-page report, interface dispatch starts to show up in profiles. We kept it out.
TrueType subsetter that doesn't re-walk on every operation. gofpdf's font machinery re-reads the cmap table on every glyph lookup; gpdf resolves once and caches. For Latin-only content this barely matters. For CJK, where a single paragraph might touch 150 unique glyphs across Kanji, Hiragana, Katakana, and punctuation, it's the gap between "fast enough for synchronous generation" and "push to a queue."
A caveat we'll volunteer that the benchmark table won't: absolute speed matters less than people think for most PDF workloads. The interesting threshold is "fast enough to generate on the request path." Every library in this comparison clears that threshold for a single hello-world page. Only gpdf clears it for a 100-page report with repeating chrome. If your biggest document is a one-page receipt, all four maintained libraries are fine; pick by API ergonomics and license instead.
Dependencies
What go mod graph prints after a fresh go get of each:
| Library | External modules | Transitive archived deps |
|---|---|---|
| gpdf (core) | 0 | — |
| signintech/gopdf | 0 | — |
| gofpdf | 0 (but itself is archived) | itself |
| go-pdf/fpdf | 0 (but itself is archived) | itself |
| johnfercher/maroto v2 | gofpdf (archived 2021) | yes — gofpdf |
| unidoc/unipdf | many (imaging, crypto, compression) | none archived |
For teams with a "no archived deps in production" lint rule, Maroto v2 today fails it via its gofpdf transitive. The Maroto maintainers have been rewriting the backend off gofpdf for over a year; when that ships, this row changes. Worth checking the Maroto repo before making a decision on this basis — the state may have moved between when we wrote this and when you're reading it.
Licensing
| Library | License |
|---|---|
| gpdf (core) | MIT |
| signintech/gopdf | MIT |
| johnfercher/maroto v2 | MIT |
| gofpdf | MIT |
| go-pdf/fpdf | MIT |
| unidoc/unipdf | AGPL-3.0 or commercial license |
Unidoc's AGPL terms are strict. If you use it in a server that users interact with over a network, your server code has to be released under AGPL too — a non-starter for most closed-source SaaS. That usually leaves the commercial license as the only real option, and their pricing isn't published publicly: plan on a conversation with sales.
This is the single biggest thing people miss in GitHub-star-count comparisons. Unidoc has the most features and the most stars. It also has a license that closes the door on most commercial use cases without a purchase. We don't say this as a dig at unidoc — their model is a legitimate one and the product is excellent — but you should know before go get.
Maintenance status, plain-English edition
- gpdf — primary maintainer is this team (gpdf-dev). Active releases every 2–4 weeks; roadmap in the repo; CI runs on Go 1.22 through 1.26; issue response within a few business days on the main repo. We have skin in the game.
- signintech/gopdf — active with a smaller commit cadence. Issues get attention; PRs tend to merge within a few weeks. Primary use case remains low-level generation.
- Maroto v2 — active. The v2 rewrite landed in 2023 and is stable. The gofpdf dependency is known and the team is working on replacing it; check the repo for current state before committing.
- gofpdf — archived 2021-09-08. Banner on the repo: "This repository has been archived by the owner. It is now read-only." No security patches, no bug fixes.
- go-pdf/fpdf — archived in 2025. The README now recommends using a different library. We wrote a dedicated migration guide: gofpdf is archived. Here's how to migrate to gpdf..
- unidoc/unipdf — active, commercial team, well-resourced. Enterprise support is available.
How to pick
We're going to try an actual decision tree rather than a feature matrix, because "most features" isn't usually the right question:
- "I have a Go codebase that generates invoices, reports, or documents, I want MIT, zero deps, and my documents sometimes contain CJK." → gpdf.
- "I'm doing low-level PDF generation with custom geometry and I want a small, stable, hand-on-the-wheel library." → signintech/gopdf.
- "I already have Maroto-flavored layout code that works today." → stay on Maroto v2 until the gofpdf-removal lands, then re-evaluate. The API isn't the problem.
- "I need PDF/A, OCR, redaction, digital signatures, and my employer will pay for a commercial license." → unidoc/unipdf, with the license conversation up front.
- "I'm still on gofpdf and it works." → fine today. Plan the migration before the next CVE lands in an unrelated dependency and you're stuck on unmaintained code. Migration guide here.
- "Pixel-perfect HTML/CSS to PDF rendering." → none of the above. Use go-rod or chromedp with a headless Chromium, and plan for the cold-start cost.
We ship gpdf, so of course we think gpdf is the right default for the first bucket and most of the fifth. Read the benchmark code, run it locally, don't take the table at face value.
A 30-line gpdf example
Because "fastest" and "smallest dep graph" only matter if the code is bearable to read. This is a complete, runnable invoice page — no pseudo-code, no missing imports:
package main
import (
"log"
"os"
"github.com/gpdf-dev/gpdf"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/pdf"
"github.com/gpdf-dev/gpdf/template"
)
func main() {
doc := gpdf.NewDocument(
gpdf.WithPageSize(document.A4),
gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
)
page := doc.AddPage()
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("INVOICE #2026-0042", template.Bold(), template.FontSize(20))
c.Spacer(document.Mm(6))
c.Table(
[]string{"Description", "Qty", "Unit", "Amount"},
[][]string{
{"Frontend dev", "40 hrs", "$150.00", "$6,000.00"},
{"Backend dev", "60 hrs", "$150.00", "$9,000.00"},
{"UI design", "20 hrs", "$120.00", "$2,400.00"},
},
template.ColumnWidths(50, 15, 15, 20),
template.TableHeaderStyle(
template.Bold(),
template.TextColor(pdf.White),
template.BgColor(pdf.RGBHex(0x1A237E)),
),
)
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("invoice.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
Zero SetXY. Zero manual column-width math. Swap "Description" for "品目" and add gpdf.WithFont("NotoSansJP", ttfBytes) to the document options — it renders Japanese without any other change.
What we chose to leave out
Every comparison post has a section for "omitted because of X." Ours:
- Private gofpdf forks. There are production forks inside companies. We couldn't benchmark code we can't see.
pdfcpu. It's in every list of "Go PDF libraries," but it's primarily a PDF processor (merge, split, encrypt, stamp), not a generator. Out of scope for this post; a processing-oriented article is planned separately.- Anything wrapping
gotenbergor a headless-browser service. Not a library. Not a fair comparison. - Our own
gpdfbenchmarks. The core numbers are what matters for the comparison.
FAQ
Why is gpdf 10× faster than gofpdf? What's the trick? No single trick. Three designs compound: single-pass layout (no AST between builder and writer), concrete types on the hot path, and a TrueType subsetter that caches the cmap. Any one of these in isolation gives a 2× gain. Stacked, it's an order of magnitude.
Can I actually re-run this benchmark?
Yes. git clone https://github.com/gpdf-dev/gpdf && cd gpdf/_benchmark && go test -bench=. -benchmem. If the numbers don't match what's in this post — same machine architecture, same Go version — open an issue. Benchmark drift is real; we'd rather know.
Is gofpdf coming back? Realistically, no. The last commit is from 2021. The issue tracker is closed. Even if someone re-opened it, the architecture (single cursor, single-byte fonts, no grid) is the wrong starting point for 2026. Better to treat it as a historical artifact and migrate.
What about Java iText / Python ReportLab / Node pdfkit? Cross-language benchmarks are a different post. Short version: Go generally wins on throughput and cold-start, loses on feature breadth (especially on HTML-to-PDF fidelity). For teams already on Go, gpdf is faster and smaller than any of those cross-language options; for teams on Python or Node, the migration cost usually only pays back at high-volume scale.
Is this comparison going to stay fair if gpdf's competitors improve?
Yes. We run this every year. If signintech/gopdf ships a table API that halves its time, that's in the 2027 post. If Maroto v2 finishes removing gofpdf, that row changes. The benchmark code is public specifically so nobody has to take our word for it.
Try gpdf
gpdf is a Go library for generating PDFs. MIT, zero dependencies, native CJK.
go get github.com/gpdf-dev/gpdf
⭐ Star on GitHub · Read the docs
Next reads
- gofpdf is archived. Here's how to migrate to gpdf. — the full API mapping with five before/after code pairs.
- Quickstart — five-minute setup, including
go.mod. - The benchmark code itself:
_benchmark/benchmark_test.go.