PDF Metadata and Accessibility: What the EAA Means for Your Documents
The European Accessibility Act (EAA) came into force on June 28, 2025. If you generate PDFs for customers in the EU — invoices, contracts, statements, tickets, reports — those documents likely fall under its scope. And most PDFs generated by typical backend pipelines (Puppeteer screenshots, wkhtmltopdf, older PDFKit setups) fail the accessibility bar by default.
This post covers what the EAA actually requires from PDFs, what metadata and structure you need to add, and how to check compliance programmatically.
What the EAA actually requires for PDFs
The EAA (Directive 2019/882) applies to a broad set of "products and services" including e-commerce, banking, e-books, and transportation ticketing. When those services deliver documents to consumers, the documents themselves must be accessible.
For PDFs, the practical standard is PDF/UA-1 (ISO 14289-1), which builds on tagged PDF and requires:
- A logical structure tree (tagged content: headings, paragraphs, lists, tables, figures)
- Meaningful document metadata (title, language, author)
- Alt text for all non-text content
- Correct reading order independent of visual layout
- Unicode mappings for all glyphs
- The document marked as tagged and as PDF/UA compliant in its metadata
WCAG 2.1 AA is the underlying accessibility bar. PDF/UA is how you meet it in the PDF format.
The metadata baseline
Every compliant PDF needs a few metadata fields set correctly. This is the low-hanging fruit and the most common place generators fail.
The document catalog needs:
txt/Lang (e.g. "en-US") /MarkInfo << /Marked true >> /ViewerPreferences << /DisplayDocTitle true >> /Metadata (XMP stream)
And the XMP metadata stream needs dc:title, dc:language, and the PDF/UA identifier:
xml<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:pdfuaid="http://www.aiim.org/pdfua/ns/id/"> <dc:title> <rdf:Alt> <rdf:li xml:lang="x-default">Invoice INV-2025-0042</rdf:li> </rdf:Alt> </dc:title> <dc:language> <rdf:Bag><rdf:li>en-US</rdf:li></rdf:Bag> </dc:language> <pdfuaid:part>1</pdfuaid:part> </rdf:Description>
Without pdfuaid:part, validators will not recognize the document as claiming PDF/UA conformance, even if the structure is perfect.
Tagging is the hard part
Metadata is easy. Tagging content correctly is where most pipelines break.
A tagged PDF describes its content as a tree of semantic elements, similar to HTML:
txtDocument ├── H1 "Invoice INV-2025-0042" ├── P "Issued on 12 November 2025" ├── Table │ ├── TR (with TH cells) │ └── TR (with TD cells) └── P "Total: €1,240.00"
If you generate PDFs from HTML with Puppeteer or Chromium's print-to-PDF, you get no tag tree. Chromium supports tagging via --export-tagged-pdf, but the output is inconsistent and often fails PDF/UA validation on tables and forms.
If you use PDFKit (Node.js) directly, tagging requires manually calling struct() for every semantic element:
tsimport PDFDocument from "pdfkit"; const doc = new PDFDocument({ pdfVersion: "1.7", tagged: true, lang: "en-US", displayTitle: true, info: { Title: "Invoice INV-2025-0042" }, }); const heading = doc.struct("H1", () => { doc.fontSize(20).text("Invoice INV-2025-0042"); }); doc.addStructure(heading);
This works, but you need to do it for every element in every template. It scales badly.
Validating your output
Do not ship "accessible" PDFs on faith. Use a validator in CI.
The reference tool is veraPDF, which validates against PDF/UA-1 and the PDF/A family. It has a CLI you can drop into a pipeline:
bashverapdf --flavour ua1 --format json invoice.pdf > report.json
A passing document returns "compliant": true in the report. You can fail your build on any violation:
bashverapdf --flavour ua1 invoice.pdf | grep -q "PASS" || exit 1
For a quicker developer loop, the PAC 2024 GUI tool (free, Windows) is the most thorough visual checker. Adobe Acrobat's built-in accessibility checker is useful but less strict than veraPDF.
A practical checklist
Before you ship, verify each generated PDF has:
-
/Langset on the document catalog -
Titleset in the info dictionary and in XMPdc:title -
DisplayDocTitleviewer preference set totrue -
MarkInfo /Marked true -
pdfuaid:part = 1in XMP metadata - A complete structure tree with correct semantic tags
- Alt text (
/Alt) on every image - [ ]