PDF Metadata and Accessibility: What the EAA Means for Your Documents

The European Accessibility Act takes effect June 2025, and PDFs are squarely in scope. Here's what backend developers need to know about metadata, tagging, and PDF/UA compliance.

pdfaccessibilityeaapdf-uacompliancenode.js

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:

txt
Document ├── 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:

ts
import 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:

bash
verapdf --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:

bash
verapdf --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:

  • /Lang set on the document catalog
  • Title set in the info dictionary and in XMP dc:title
  • DisplayDocTitle viewer preference set to true
  • MarkInfo /Marked true
  • pdfuaid:part = 1 in XMP metadata
  • A complete structure tree with correct semantic tags
  • Alt text (/Alt) on every image
  • [ ]