Folio: a Go PDF library that lays out pages itself, without a browser
PDF library for Go: layout engine, HTML to PDF, forms, signatures, barcodes, and PDF/A. Apache 2.0.
At a glance
- What is it?
- Folio is a PDF library for Go with its own layout engine, an in-process HTML to PDF path, script-aware text shaping for RTL, Indic and CJK, plus forms, signatures, barcodes and PDF/A support. The trade-off is that the HTML support is a documented subset rather than a browser engine.
- Who is it for?
- Adopt Folio when you generate PDFs from Go and want one in-process library for layout, HTML conversion, forms, signatures and barcodes, and when your documents include Arabic, Hebrew, Devanagari or CJK text that needs real shaping rather than a fallback. Do not adopt it when your HTML comes from the open web and depends on browser behaviour, since the CSS support is a subset with a documented list of known gaps in docs/CSS_SUPPORT.md.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A PDF library that owns the whole pipeline in Go
Folio describes itself as a PDF library for Go, and the scope in the repository description is wide: layout engine, HTML to PDF, forms, signatures, barcodes, and PDF/A. The README adds text shaping for left-to-right, right-to-left, Indic and CJK scripts, redaction, and page import. The primary language is Go, the licence is Apache 2.0, and the default branch is main.
What makes it more than a PDF writer is that the layout engine is part of the project. Folio is not calling out to a browser, not shelling out to a converter and not shelling out to a service. The HTML path runs in process, with no subprocess, no headless browser and no external service, as the README states. The same is true of the typography: the script-specific shapers and the OpenType tables they consume ship with the library.
The audience is a Go developer who has to emit a PDF and wants to stay inside the Go program. That covers invoice and report generation, documents with AcroForm fields, files that need a digital signature, and archives that must conform to PDF/A. A playground at playground.foliopdf.dev lets you try the conversion in a browser, and the README lists a Java SDK, dev.foliopdf:folio-java, published on Maven Central, with the Go package as the reference implementation in this repository.
Layout is a pure function, which is why page breaks are predictable
The layout engine is described as plan-based: layout is a pure function with no mutation during rendering, and elements can be laid out multiple times safely, which the README says makes page break splitting clean and predictable.
That design choice is the heart of the library. In a renderer that mutates state as it draws, asking where a paragraph breaks across a page means re-running a partially consumed process. In a plan-based engine, the layout pass produces a description of where everything goes, and the split point can be recomputed from a clean state. The practical payoff is that a table row that does not fit gets split at a repeatable place rather than at wherever the previous draw left the cursor.
The element vocabulary is broad and is listed in a table in the README. Paragraph handles word-wrapped text with alignment, leading, and orphan and widow control. Heading covers H1 to H6 with preset sizes, spacing and auto-bookmarks. Table covers borders, colspan, rowspan, header repetition and auto column widths. List covers bullet, numbered, Roman, alpha and nested. Div is a container with borders, background and padding. Flex is a flexbox layout with direction, wrap and alignment. Image handles JPEG, PNG and TIFF with aspect ratio preservation. LineSeparator draws solid, dashed or dotted rules, TabbedLine provides tab stops with dot leaders for tables of contents, Link makes clickable text, Float handles left and right floating with text wrap, Columns does multi-column layout with automatic balancing, AreaBreak is an explicit page break, and BarcodeElement embeds Code128, QR and EAN-13 codes inline in the flow.
The library also builds a table of contents with leaders and a document outline from headings, and SetAutoBookmarks is available on the document.
AddHTML is the entry point, and ConvertFull is the escape hatch
Passing an HTML string to a document with AddHTML is the canonical entry point, and the README explains what folio does with it. The converted content is wired into the document, covering normal-flow elements, @page size and margins, running headers and footers, and absolutely positioned or position:fixed elements alike.
The README also draws a sharp line around a common mistake. When you need the raw conversion result to inspect or transform it first, use html.ConvertFull and add it back with doc.AddConvertResult(result). Adding result.Elements by hand drops absolutes, margin boxes and the @page configuration, so the document loses page size, margins and running headers without any error to tell you.
CSS support is listed as 40+ HTML elements, inline and style block CSS, flexbox, CSS grid, SVG, named, hex and rgb colors, @page rules, multi-level list numbering via CSS counters, and tables with colspan and rowspan. The full list of recognized CSS properties, the accepted value forms and the known unsupported features are in docs/CSS_SUPPORT.md, which is the file to read before promising a client that a template will render.
That is the honest boundary of the project. It implements a subset of CSS with a documented coverage list, not the behaviour of a browser engine, and the difference between the two shows up in the hard cases: unusual layout, web fonts from a network, scripts that animate.
Text shaping decides whether a document is readable in Arabic or Devanagari
The typography table in the README shows four script families and the pipeline behind each. Left-to-right scripts, covering Latin, Cyrillic and Greek, use a standard glyph run with GSUB ligatures and GPOS kerning. Right-to-left scripts, covering Arabic, Hebrew and Farsi, use the UAX #9 bidirectional algorithm, Arabic contextual shaping for the initial, medial, final and isolated forms via GSUB, kashida justification, and /ActualText markers for copy and paste fidelity. Indic scripts, with Devanagari as the named example, use a five-phase OpenType pipeline of reordering, half-form substitution and conjunct formation. CJK, covering Japanese, Chinese and Korean, uses an embedded TrueType subset and line breaking with JIS X 4051 kinsoku shori.
The dispatch mechanism matters as much as the individual pipelines. The engine segments text by Unicode script under UAX #24, splits by grapheme cluster under UAX #29, and hands each run to the matching shaper. HTML conversion inherits the same pipeline, and adding dir="rtl" or direction: rtl flips paragraph and list direction.
The /ActualText detail is the one most people miss. Without it, text copied out of an Arabic PDF can come back in visual order rather than logical order, which breaks search and breaks screen readers. A library that bothers to emit those markers is doing something that a plain text drawing API will not do for you. The README points at runnable demos in examples/rtl and examples/indic.
Installing takes one command and the first document takes ten lines
The install step is the standard Go fetch:
go get github.com/carlos7ags/folioFolio requires Go 1.25 or later, and the go.mod file declares go 1.25.0. The dependency list is deliberately small. The README names three external dependencies, all from the Go x extended standard library: golang.org/x/image for font parsing and TIFF decoding, golang.org/x/net for HTML parsing, and golang.org/x/text for the Unicode bidirectional algorithm used for RTL text. The manifest pins golang.org/x/image at v0.45.0, golang.org/x/net at v0.58.0 and golang.org/x/text at v0.41.0.
The first document creates a document, sets its metadata, turns on auto-bookmarks, adds a heading and a paragraph, and saves:
func main() {
doc := document.NewDocument(document.PageSizeA4)
doc.Info.Title = "Hello World"
doc.SetAutoBookmarks(true)
doc.Add(layout.NewHeading("Hello, Folio!", layout.H1))
doc.Add(layout.NewParagraph(
"A PDF generated from Go code.",
font.Helvetica, 12,
))
doc.Save("hello.pdf")
}The API is a builder style. Every constructor returns a value you can keep configuring, for example a paragraph with SetAlign(layout.AlignJustify) and SetSpaceAfter(10), or a styled paragraph assembled from runs with WithColor(layout.ColorRed) and WithUnderline().
The HTML path looks just as short. The document is created with a page size and the markup is handed to AddHTML with a nil second argument:
doc := document.NewDocument(document.PageSizeLetter)
doc.AddHTML(`
<h1>Invoice #1042</h1>
<p>Bill to: <strong>Acme Corp</strong></p>
<table border="1">
<tr><th>Item</th><th>Amount</th></tr>
<tr><td>Consulting</td><td>$1,200</td></tr>
</table>
`, nil)
doc.Save("invoice.pdf")What you should see on disk is a PDF with a single page, a heading, a paragraph and a bordered table, and with no browser or converter process involved in producing it.
A repository of many packages, plus a WASM and a C ABI build
The top level of the repository is a map of the feature set. Alongside document/, layout/, font/, html/, image/ and core/ sit barcode/, forms/, sign/, reader/, optimize/, export/, svg/, unicode/, content/, tmpl/ and zugferd/, which correspond to the capabilities the README advertises: barcodes, AcroForm support, digital signatures, reading existing PDFs, optimization, SVG input, script handling, content and templates, and ZUGFeRD. The document/, layout/ and font/ packages are the ones the quick start imports.
Documentation is part of the tree rather than a separate site. ARCHITECTURE.md, CHANGELOG.md, MIGRATING.md, CONTRIBUTING.md, CODE_OF_CONDUCT.md, NOTICE and a docs/ directory sit at the root, and examples/ holds runnable directories for the cases people actually hit: barcodes, cjk, fonts, forms, html-to-pdf, import-page, indic, invoice, legal-numbering, links, list-styling, merge, optimize, redact, report, rtl, sign, stress-tests, table-border-collapse, table-rowspan, template and zugferd.
The Makefile shows how the project is built and checked. The test target runs the race detector with a timeout:
go test ./... -race -count=1 -timeout 300sThere are separate builds for the browser and for C consumers. The wasm target produces folio.wasm with GOOS=js GOARCH=wasm, which is what backs the playground. The cshared target builds a shared library with -buildmode=c-shared, and a test-cabi target compiles a C file against it with cc, so the exported C interface has a checked test rather than a promise.
The licence is Apache 2.0, with a NOTICE file alongside the LICENSE, and the release history shows v0.9.1 on 2026-06-10, v0.10.0 on 2026-07-10 and v0.10.1 on 2026-08-14. The last push was on 2026-10-17, so the code is moving, and the version is still below 1.0, which is why MIGRATING.md matters when you upgrade.
Where Folio loses to a headless browser
The honest comparison is with printing a page from a real browser, which handles the same job with a different engine. A browser gives you the CSS the engine implements, including the awkward cases, and it renders whatever the web already renders. Folio gives you a documented subset, an in-process library with no browser to install, no subprocess to supervise and no remote service to call, and typography support for right-to-left, Indic and CJK scripts that is built in rather than bolted on.
So the choice depends on where the HTML comes from. If it is a template your team controls, with a bounded set of elements and styles, Folio gives a reproducible result and a small dependency footprint. If it is arbitrary HTML from the open web, or a design that relies on browser-specific layout behaviour, the same input will not render the same way, and the difference will surface in the parts of the document nobody checked.
There are capability limits too, and they are named rather than hidden. The CSS support file lists recognized properties, accepted value forms and known unsupported features, and the README notes that adding result.Elements by hand rather than using doc.AddConvertResult silently drops absolutes, margin boxes and @page configuration. Both are failure modes that show up as a wrong document rather than an error.
A second limitation is version churn. The project is at 0.10.x, several releases landed in a couple of months, and MIGRATING.md exists precisely because API changes between versions are expected. Treat the version number as a moving target rather than a stable dependency, and read the migration notes before bumping.
Editorial conclusion
Adopt Folio when you generate PDFs from Go and want one in-process library for layout, HTML conversion, forms, signatures and barcodes, and when your documents include Arabic, Hebrew, Devanagari or CJK text that needs real shaping rather than a fallback. Do not adopt it when your HTML comes from the open web and depends on browser behaviour, since the CSS support is a subset with a documented list of known gaps in docs/CSS_SUPPORT.md. Before you commit, read that support file against your own templates, and if you upgrade across a minor version, check MIGRATING.md, because the project is still at version 0.10.x and the release history shows several changes in the last few months.
Frequently asked questions
How do I install Folio in a Go project?
The README gives the install step as go get github.com/carlos7ags/folio, and states that Folio requires Go 1.25 or later. The go.mod file declares go 1.25.0 and depends on golang.org/x/image, golang.org/x/net and golang.org/x/text.
Does Folio convert HTML to PDF without a headless browser?
Yes. The README states that AddHTML wires converted content into the document and runs in process, with no subprocess, no headless browser and no external service. When you need the raw result first, use html.ConvertFull and add it back with doc.AddConvertResult(result).
Which scripts does Folio shape correctly?
The README covers left-to-right scripts, right-to-left scripts such as Arabic and Hebrew using the UAX #9 bidirectional algorithm, Indic scripts with a five-phase OpenType pipeline, and CJK with an embedded TrueType subset and JIS X 4051 line breaking. Text is segmented by Unicode script under UAX #24 and split by grapheme cluster under UAX #29.
What does Folio support besides basic text and tables?
The README lists forms, digital signatures, barcodes, redaction, page import, PDF/A compliance and ZUGFeRD among the feature areas, and the repository contains corresponding packages including forms/, sign/, barcode/, reader/ and zugferd/. Layout elements include flexbox, floats, multi-column layout, links, tab stops and inline barcodes.
What licence is Folio released under?
Folio is released under the Apache 2.0 licence, with a LICENSE file and a NOTICE file at the root of the repository. The most recent releases listed are v0.10.1 on 2026-08-14 and v0.10.0 on 2026-07-10.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/carlos7ags-folio)Was this useful?