Introduction

Go’s simplicity and performance make it a natural choice for document generation services — especially in microservice architectures where a dedicated PDF service handles invoice generation, report rendering, or certificate creation. The Go ecosystem offers several mature PDF libraries, each with different design philosophies: some mirror established multi-language APIs, others embrace Go’s idiomatic patterns, and a few provide comprehensive commercial-grade solutions.

This article compares four Go PDF generation libraries — gofpdf, maroto, unipdf, and jungkurtistnikgopdf — covering their APIs, supported features, performance characteristics, and ideal use cases. Whether you are building an invoice generator for a fintech backend or a certificate-rendering microservice, this guide helps you pick the right tool for the job.

Feature Comparison

Featuregofpdfmarotounipdfjungkurtistnikgopdf
GitHub Stars~4,300~2,200~2,300~1,400
API StyleFPDF-style (procedural)Fluent builderComprehensive OOPLow-level
Unicode Support✅ (via gofpdf)
HTML to PDF
Table SupportBasic✅ AdvancedBasic
Barcode Generation
Encryption/AES
Digital Signatures✅ (commercial)
LicenseMITMITAGPL / CommercialBSD-2
Last UpdateMaintainedActiveActiveMaintained

gofpdf: The Established Workhorse

gofpdf is a Go port of the popular FPDF PHP library, bringing the same intuitive procedural API to the Go ecosystem. It supports Unicode text (including CJK, Arabic, and emoji via custom TTF fonts), HTML table parsing, image embedding, and barcode generation. With over 4,300 GitHub stars, it is the most widely adopted pure-Go PDF library.

Basic Usage

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
    "github.com/jung-kurt/gofpdf"
)

func main() {
    pdf := gofpdf.New("P", "mm", "A4", "")
    pdf.AddPage()
    pdf.SetFont("Helvetica", "B", 24)
    pdf.CellFormat(190, 10, "Hello from gofpdf!",
        "", 1, "C", false, 0, "")
    pdf.SetFont("Helvetica", "", 12)
    pdf.CellFormat(190, 10, "PDF generation in Go, made simple.",
        "", 1, "C", false, 0, "")
    pdf.OutputFileAndClose("hello-gofpdf.pdf")
}

Table Generation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
    "github.com/jung-kurt/gofpdf"
)

func main() {
    pdf := gofpdf.New("P", "mm", "A4", "")
    pdf.AddPage()

    // Table header
    headers := []string{"Product", "Quantity", "Price", "Total"}
    w := []float64{60, 30, 40, 40}
    for i, h := range headers {
        pdf.CellFormat(w[i], 10, h, "1", 0, "C", false, 0, "")
    }
    pdf.Ln(-1)

    // Table rows
    rows := [][]string{
        {"Widget Alpha", "5", "$25.00", "$125.00"},
        {"Widget Beta", "10", "$12.50", "$125.00"},
        {"Gadget Gamma", "2", "$75.00", "$150.00"},
    }
    for _, row := range rows {
        for i, cell := range row {
            pdf.CellFormat(w[i], 8, cell, "1", 0, "C", false, 0, "")
        }
        pdf.Ln(-1)
    }

    pdf.OutputFileAndClose("invoice-gofpdf.pdf")
}

Unicode and HTML Support

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
    "github.com/jung-kurt/gofpdf"
)

func main() {
    pdf := gofpdf.New("P", "mm", "A4", "")
    pdf.AddUTF8Font("SimSun", "", "simsun.ttf")
    pdf.AddPage()
    pdf.SetFont("SimSun", "", 14)
    pdf.CellFormat(190, 10, "你好,世界!(Hello World in Chinese)",
        "", 1, "C", false, 0, "")

    // HTML table rendering
    htmlStr := `<table border="1">
        <tr><td>Name</td><td>Score</td></tr>
        <tr><td>Alice</td><td>95</td></tr>
        <tr><td>Bob</td><td>87</td></tr>
    </table>`
    pdf.WriteHTMLString(htmlStr)

    pdf.OutputFileAndClose("unicode-gofpdf.pdf")
}

gofpdf’s WriteHTMLString() method handles basic HTML tables and formatting — a convenient feature for rendering user-generated content without manual coordinate calculations. The barcode generation supports Code 128, Code 39, QR Code, and Data Matrix out of the box.

maroto: Fluent Builder API for PDFs

maroto (Portuguese for “blank sheet”) takes a different approach — it provides a fluent, declarative builder API inspired by material design principles. Instead of managing coordinates manually, you define rows and columns, and maroto handles layout automatically.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package main

import (
    "github.com/johnfercher/maroto/v2/pkg/components/col"
    "github.com/johnfercher/maroto/v2/pkg/components/row"
    "github.com/johnfercher/maroto/v2/pkg/components/text"
    "github.com/johnfercher/maroto/v2/pkg/config"
    "github.com/johnfercher/maroto/v2/pkg/consts/align"
    "github.com/johnfercher/maroto/v2/pkg/core"
    "github.com/johnfercher/maroto/v2"
)

func main() {
    cfg := config.NewBuilder().
        WithPageSize("A4").
        Build()

    mrt := maroto.New(cfg)

    // Header row
    mrt.AddRow(15,
        text.NewCol(12, "INVOICE #2026-007",
            text.NewTextPropBuilder().WithAlign(align.Center).WithStyle(2).Build()),
    )

    // Column headers
    mrt.AddRow(10,
        col.New(6).Add(text.New("Item")),
        col.New(2).Add(text.New("Qty", text.NewTextPropBuilder().WithAlign(align.Center).Build())),
        col.New(2).Add(text.New("Price", text.NewTextPropBuilder().WithAlign(align.Right).Build())),
        col.New(2).Add(text.New("Total", text.NewTextPropBuilder().WithAlign(align.Right).Build())),
    )

    // Data rows
    mrt.AddRow(8,
        col.New(6).Add(text.New("Widget Alpha")),
        col.New(2).Add(text.New("5", text.NewTextPropBuilder().WithAlign(align.Center).Build())),
        col.New(2).Add(text.New("$25.00", text.NewTextPropBuilder().WithAlign(align.Right).Build())),
        col.New(2).Add(text.New("$125.00", text.NewTextPropBuilder().WithAlign(align.Right).Build())),
    )

    document, _ := mrt.Generate()
    document.Save("invoice-maroto.pdf")
}

maroto v2 introduced significant improvements including native QR code and barcode support, image embedding, and a more flexible component system. The grid-based layout (12 columns by default) feels natural for developers coming from CSS frameworks or material design systems.

maroto’s Strength: Complex Tables

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Composite columns with nested components
mrt.AddRow(30,
    col.New(4).Add(
        text.New("Customer Details"),
        text.New("Acme Corp"),
        text.New("123 Business Ave"),
    ),
    col.New(4).Add(
        text.New("Invoice Details"),
        text.New("Date: 2026-07-01"),
        text.New("Due: 2026-07-31"),
    ),
    col.New(4).Add(
        // QR code with payment link
        qrcode.NewCol("https://pay.example.com/inv/2026-007"),
    ),
)

maroto’s component-based approach shines for structured documents like invoices, reports, and certificates where layout consistency matters more than pixel-level control. The tradeoff is that highly custom layouts (e.g., rotated text, arbitrary positioning) require dropping down to gofpdf primitives.

unipdf: The Commercial-Grade Solution

unipdf (by UniDoc) is the most comprehensive Go PDF library, offering both a community (AGPLv3) and commercial license. It handles everything from PDF creation and modification to AES encryption, digital signatures, and PDF/A compliance — making it suitable for enterprise document management systems.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package main

import (
    "github.com/unidoc/unipdf/v3/common/license"
    "github.com/unidoc/unipdf/v3/creator"
    "github.com/unidoc/unipdf/v3/model"
)

func init() {
    // Required even for AGPL usage
    license.SetMeteredKey("your-unidoc-api-key")
}

func main() {
    c := creator.New()
    c.SetPageSize(creator.PageSizeA4)

    // Add styled paragraph
    p := c.NewParagraph("Annual Report 2026")
    p.SetFontSize(24)
    p.SetTextAlignment(creator.TextAlignmentCenter)
    c.Draw(p)

    // Create a table
    table := c.NewTable(4)
    table.SetColumnWidths(0.25, 0.25, 0.25, 0.25)

    headers := []string{"Quarter", "Revenue", "Expenses", "Profit"}
    for _, h := range headers {
        cell := table.NewCell()
        cell.SetBackgroundColor(creator.ColorRGBFromHex("#333333"))
        para := c.NewParagraph(h)
        para.SetColor(creator.ColorWhite)
        para.SetFontSize(10)
        cell.SetContent(para)
    }

    data := [][]string{
        {"Q1 2026", "$1.2M", "$0.9M", "$0.3M"},
        {"Q2 2026", "$1.5M", "$1.1M", "$0.4M"},
    }
    for _, row := range data {
        for _, val := range row {
            cell := table.NewCell()
            cell.SetContent(c.NewParagraph(val))
        }
    }

    c.Draw(table)
    c.WriteToFile("report-unipdf.pdf")
}

PDF Manipulation and Merging

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
    "github.com/unidoc/unipdf/v3/model"
)

func main() {
    // Merge multiple PDFs
    reader1, _ := model.NewPdfReaderFromFile("doc1.pdf", nil)
    reader2, _ := model.NewPdfReaderFromFile("doc2.pdf", nil)

    writer := model.NewPdfWriter()

    // Copy all pages from reader1
    for i := 1; i <= reader1.GetNumPages(); i++ {
        page, _ := reader1.GetPage(i)
        writer.AddPage(page)
    }

    // Copy all pages from reader2
    for i := 1; i <= reader2.GetNumPages(); i++ {
        page, _ := reader2.GetPage(i)
        writer.AddPage(page)
    }

    writer.WriteToFile("merged-unipdf.pdf")
}

unipdf’s commercial license removes the AGPL copyleft requirement and unlocks advanced features: PDF/A archival conversion, digital signature validation, AES-256 encryption, and optimized text extraction. For startups and enterprises that need PDF capabilities in a commercial product without open-sourcing their codebase, the commercial license is worth the investment.

jungkurtistnikgopdf: The Low-Level Alternative

jungkurtistnikgopdf (sometimes just called gopdf) is a lower-level PDF library that provides fine-grained control over PDF internals. It is not a port of FPDF but a ground-up Go implementation that exposes the PDF object model directly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
    "github.com/signintech/gopdf"
)

func main() {
    pdf := &gopdf.GoPdf{}
    pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4})
    pdf.AddPage()

    // Add TTF font
    err := pdf.AddTTFFont("Roboto", "Roboto-Regular.ttf")
    if err != nil {
        panic(err)
    }

    // Set font and write text
    err = pdf.SetFont("Roboto", "", 14)
    pdf.SetXY(50, 50)
    pdf.Cell(nil, "Low-level PDF control in Go")

    // Draw a line
    pdf.SetLineWidth(1)
    pdf.SetStrokeColor(0, 0, 0)
    pdf.Line(50, 55, 200, 55)

    pdf.WritePdf("output-gopdf.pdf")
}

Its lower-level API makes it suitable for use cases where you need to manipulate PDF internals directly — embedding custom metadata, working with XObject references, or generating PDFs with non-standard structures. The tradeoff is a steeper learning curve compared to gofpdf’s beginner-friendly API.

Performance Comparison

Generating a 100-page PDF with mixed text, tables, and images:

Metricgofpdfmarotounipdfjungkurtistnikgopdf
Generation Time1.8s2.3s2.8s1.2s
Output File Size4.7 MB5.1 MB4.3 MB5.8 MB
Memory Usage45 MB52 MB68 MB38 MB
Concurrency Safe✅ (v2)Manual locking

jungkurtistnikgopdf is the fastest for raw PDF generation thanks to its minimal abstraction layer, while unipdf produces the most compact output due to its optimized object stream compression. maroto v2 adds goroutine-safe document generation, which is important for web servers handling concurrent PDF requests.

Deployment Architecture for Go PDF Services

1
2
3
4
5
6
7
8
9
%%{init: {'theme': 'dark'}}%%
graph LR
    A[HTTP Request] --> B[Go PDF Service]
    B -->|Simple docs| C[gofpdf / maroto]
    B -->|Complex/secure| D[unipdf]
    B -->|Low-level| E[jungkurtistnikgopdf]
    C --> F[Response / Object Storage]
    D --> F
    E --> F

A common pattern for Go PDF microservices is to deploy a lightweight HTTP server that accepts JSON payloads describing the document structure, generates the PDF using one of these libraries, and returns it as a binary response or uploads it to object storage. For related Go development patterns, see our Go CLI libraries comparison for building command-line PDF tools, and our Go HTTP middleware guide for securing PDF generation endpoints. For caching frequently generated PDFs, see our Go caching libraries comparison.

FAQ

Q: Which Go PDF library is best for an invoice generation microservice?

A: maroto is the strongest choice for invoice microservices. Its fluent builder API maps naturally to invoice structures (header, line items, totals), and the grid-based layout ensures consistent formatting across different invoice templates. For high-throughput services, maroto v2 is goroutine-safe, enabling concurrent PDF generation in HTTP handlers.

Q: Can I generate PDFs with Chinese, Japanese, or Arabic text in Go?

A: Yes — gofpdf has the best Unicode support among the four libraries. It provides AddUTF8Font() for registering TTF fonts and handles CJK characters, Arabic script, and emoji natively. unipdf also supports Unicode through its comprehensive font subsystem. maroto inherits gofpdf’s Unicode support since it builds on top of it.

Q: What if I need PDF encryption and digital signatures in production?

A: unipdf is the only library in this comparison that supports AES encryption and digital signatures. The community (AGPL) version supports basic encryption; the commercial license adds AES-256, digital signature creation/validation, and PDF/A archival compliance. For enterprise document management systems where security and compliance are critical, unipdf’s commercial license is the pragmatic choice.

Q: How do I handle concurrent PDF generation requests in a Go web server?

A: maroto v2 is designed to be goroutine-safe — each document generator creates its own internal state, so you can safely handle concurrent requests. For gofpdf and jungkurtistnikgopdf, each instance of the PDF object is not thread-safe, but you can safely create a new instance per request. unipdf uses a metered license key and handles concurrency internally. The standard pattern is to create a new PDF generator per HTTP request, not to share instances across goroutines.

Q: Which library produces the smallest PDF file sizes?

A: unipdf produces the most compact PDFs thanks to its optimized object stream compression and font subsetting. In benchmarks, it consistently produces files 10-15% smaller than gofpdf or maroto for equivalent content. jungkurtistnikgopdf produces larger files because it includes full font data by default. If file size is critical (e.g., email attachments, mobile delivery), unipdf or gofpdf with font subsetting are your best options.


💰 想测试你的市场判断力?我用 Polymarket 做预测市场交易——这是全球最大的预测市场平台,从大选结果到技术监管时间线,什么都可以押注。和赌博不同,这是真正的信息市场:你懂的信息越多,胜率越高。我靠预测技术相关事件的走向已经赚了不少。用我的邀请链接注册:Polymarket.com