Forme 0.16.0: real PDF/A conformance, and a correction
PDF/A-2 at levels 2b, 2u, and 2a, verified by veraPDF in CI — plus an honest correction: PDF/A output before 0.16.0 was never conformant.
Forme 0.16.0 produces PDF/A-2 conforming archival documents — levels 2b, 2u, and 2a — verified by veraPDF in CI, and composable with PDF/UA-1 so a single file can be both archival and accessible. That combination is the configuration government, education, and healthcare archives actually require.
Before the feature tour, a correction that matters more than the feature.
The correction: PDF/A output before 0.16.0 was not valid PDF/A
If you used pdfa: "2b" (or "2a") in 0.15.0 or earlier, those files are not valid PDF/A. They carry an OutputIntent whose ICC profile was invalid, so no conformant PDF/A reader would have accepted them as archival.
The cause is almost embarrassing enough to be instructive. The embedded sRGB profile (srgb2014.icc) was never an ICC profile at all — the curl that fetched it around 0.9.0 returned a Cloudflare "Just a moment…" HTML challenge page instead of the binary. It was embedded unchecked and shipped in every build since. Nothing caught it because nothing validated the output.
It's now replaced with a real, generated sRGB profile (~588 bytes, ICC v4.3, Little CMS / MIT-licensed) and validated in CI — and a test asserts the embedded bytes parse as a valid ICC profile, so this specific failure can't recur.
How to check your files: anything produced with pdfa before 0.16.0 fails veraPDF's PDF/A profile — verapdf -f 2b your-file.pdf reports FAIL. Re-render with 0.16.0 (same input, same options) and it passes.
This is also why the compliance claims on this site now come with receipts: every claim names the validator, the corpus, and the CI gate that enforces it. "Nothing caught it because nothing validated it" is a failure mode we've opted out of permanently.
What verified means here
The same nine-document corpus the PDF/UA gate uses — five shipped templates plus four HTML fixtures — passes veraPDF's PDF/A-2b and PDF/A-2a profiles, rendered as PDF/A and PDF/UA-1 together, on every commit. The claim is enforced, not observed. Getting there took:
- A real sRGB OutputIntent — the fix above.
- PDF/A XMP metadata —
pdfaidpart and conformance, plus the pdfaExtension description for the PDF/UA identification schema, so both standards' metadata coexist. - A deterministic trailer
/ID— derived from a content hash rather than a timestamp or random bytes. Identical input still produces byte-identical output, so Forme's determinism guarantee (native == WASM, reproducible builds) holds in PDF/A mode too — a property archival pipelines care about. - Composition fixes —
/F(Print) on link annotations, and the font-embedding check accepts the PDF/UA metric-compatible substitution, sopdfa+pdfUano longer conflict.
Layout is untouched: archival output is geometry-identical to standard output.
Using it
import { standardFonts } from '@formepdf/fonts-standard';
<Document pdfa="2a" pdfUa lang="en-US" fonts={standardFonts()}>
…
</Document>
From the HTML path: pdfA in renderHtml options, or --pdf-a <level> on the forme-html CLI.
PDF/A requires every font to be embedded, which the built-in standard fonts aren't — that's what @formepdf/fonts-standard provides: metric-compatible replacements, so the archival document lays out exactly like the one you previewed. If no embeddable font is registered, a PDF/A render fails by name with the remedy rather than emitting a file whose metadata claims conformance it doesn't have. The refusal is the feature.