ZUGFeRD & Factur-X
Generate e-invoices that comply with the ZUGFeRD / Factur-X standard by combining PDF/A-3B output, an embedded CII XML attachment, and Factur-X XMP metadata.
What is ZUGFeRD / Factur-X?
ZUGFeRD (Zentraler User Guide des Forums elektronische Rechnung Deutschland) and Factur-X are technically the same e-invoicing standard under two names — ZUGFeRD for Germany/Austria/Switzerland, Factur-X for France and the broader EU. The standard defines a hybrid invoice format:
- A human-readable PDF that looks like a normal invoice
- A machine-readable XML file (Cross-Industry Invoice, CII) embedded inside the PDF as an attachment
- XMP metadata declaring the Factur-X profile and version
- The PDF must be PDF/A-3B (or higher) — the "3" allows arbitrary file attachments, and the "B" ensures visual fidelity
This lets accounting software parse the XML for automated processing while humans can still open the PDF and read the invoice visually.
Factur-X Profiles
The standard defines five conformance profiles, from least to most data:
| Profile | XML Filename | Description |
|---|---|---|
| MINIMUM | factur-x.xml |
Bare minimum: invoice number, date, total amount, VAT, buyer/seller. Sufficient for archiving. |
| BASIC WL | factur-x.xml |
Basic Without Lines: header-level data only (no line items). Good for simple invoices. |
| BASIC | factur-x.xml |
Adds line-item details (quantity, unit price, descriptions). Most common profile. |
| EN 16931 | factur-x.xml |
Full European standard EN 16931 compliance. Required for B2G (business-to-government) invoicing in many EU countries. |
| EXTENDED | factur-x.xml |
Maximum data: payment terms, delivery details, allowances/charges, additional references. Used for complex B2B invoicing. |
Requirements
A valid ZUGFeRD / Factur-X invoice requires three things from FolioPDF:
- PDF/A-3B base document — generated via
PdfAConformance.PdfA_3B - Embedded CII XML — attached with
Relationship = Alternative - Factur-X XMP metadata — injected via
ExtendMetadataorExtendXmpMetadata
Step-by-Step Workflow
Step 1: Generate the PDF/A-3B invoice
Create the visual invoice using the layout engine with PDF/A-3B conformance:
using FolioPDF;
using FolioPDF.Fluent;
using FolioPDF.Helpers;
using FolioPDF.Infrastructure;
// Generate the base invoice PDF
Document.Create(doc =>
{
doc.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(50);
page.Header().Row(row =>
{
row.RelativeItem().Text("ACME Corporation").FontSize(20).Bold();
row.ConstantItem(150).AlignRight().Text("INVOICE").FontSize(20).Bold()
.FontColor(Colors.Blue.Medium);
});
page.Content().Column(col =>
{
col.Spacing(10);
col.Item().Text("Invoice #2026-0042").FontSize(14).SemiBold();
col.Item().Text("Date: 2026-04-11");
col.Item().Text("Due: 2026-05-11");
col.Item().PaddingVertical(10).LineHorizontal(1).LineColor(Colors.Grey.Lighten2);
col.Item().Table(table =>
{
table.ColumnsDefinition(c =>
{
c.RelativeColumn(3);
c.ConstantColumn(60);
c.ConstantColumn(80);
c.ConstantColumn(80);
});
table.Cell().Text("Description").Bold();
table.Cell().Text("Qty").Bold();
table.Cell().Text("Unit Price").Bold();
table.Cell().Text("Amount").Bold();
table.Cell().Text("Widget Pro Annual License");
table.Cell().Text("10");
table.Cell().Text("EUR 49.00");
table.Cell().Text("EUR 490.00");
table.Cell().Text("Premium Support");
table.Cell().Text("1");
table.Cell().Text("EUR 200.00");
table.Cell().Text("EUR 200.00");
});
col.Item().PaddingVertical(10).LineHorizontal(1).LineColor(Colors.Grey.Lighten2);
col.Item().AlignRight().Text("Subtotal: EUR 690.00");
col.Item().AlignRight().Text("VAT (19%): EUR 131.10");
col.Item().AlignRight().Text("Total: EUR 821.10").Bold().FontSize(14);
});
page.Footer().AlignCenter().Text(t =>
{
t.Span("Page ");
t.CurrentPageNumber();
});
});
})
.WithMetadata(new DocumentMetadata
{
Title = "Invoice 2026-0042",
Author = "ACME Corporation",
Creator = "ACME Billing System",
Language = "en"
})
.WithSettings(new DocumentSettings
{
PdfAConformance = PdfAConformance.PdfA_3B
})
.GeneratePdf("invoice-base.pdf");
Step 2: Attach the CII XML and inject XMP metadata
Use DocumentOperation to embed the XML and set the Factur-X XMP. This is a post-processing step that runs the generated PDF through the qpdf engine:
// Factur-X XMP metadata — must match the CII XML profile
var facturxXmp = "
<fx:ConformanceLevel
xmlns:fx=""urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#"">
BASIC
</fx:ConformanceLevel>
<pdfaExtension:schemas
xmlns:pdfaExtension=""http://www.aiim.org/pdfa/ns/extension/""
xmlns:pdfaSchema=""http://www.aiim.org/pdfa/ns/schema#""
xmlns:pdfaProperty=""http://www.aiim.org/pdfa/ns/property#"">
<rdf:Bag>
<rdf:li rdf:parseType=""Resource"">
<pdfaSchema:schema>Factur-X PDFA Extension Schema</pdfaSchema:schema>
<pdfaSchema:namespaceURI>urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#</pdfaSchema:namespaceURI>
<pdfaSchema:prefix>fx</pdfaSchema:prefix>
<pdfaSchema:property>
<rdf:Seq>
<rdf:li rdf:parseType=""Resource"">
<pdfaProperty:name>DocumentFileName</pdfaProperty:name>
<pdfaProperty:valueType>Text</pdfaProperty:valueType>
<pdfaProperty:category>external</pdfaProperty:category>
<pdfaProperty:description>Name of the embedded XML invoice file</pdfaProperty:description>
</rdf:li>
<rdf:li rdf:parseType=""Resource"">
<pdfaProperty:name>DocumentType</pdfaProperty:name>
<pdfaProperty:valueType>Text</pdfaProperty:valueType>
<pdfaProperty:category>external</pdfaProperty:category>
<pdfaProperty:description>INVOICE</pdfaProperty:description>
</rdf:li>
<rdf:li rdf:parseType=""Resource"">
<pdfaProperty:name>Version</pdfaProperty:name>
<pdfaProperty:valueType>Text</pdfaProperty:valueType>
<pdfaProperty:category>external</pdfaProperty:category>
<pdfaProperty:description>Version of the Factur-X XML schema</pdfaProperty:description>
</rdf:li>
<rdf:li rdf:parseType=""Resource"">
<pdfaProperty:name>ConformanceLevel</pdfaProperty:name>
<pdfaProperty:valueType>Text</pdfaProperty:valueType>
<pdfaProperty:category>external</pdfaProperty:category>
<pdfaProperty:description>Factur-X conformance level</pdfaProperty:description>
</rdf:li>
</rdf:Seq>
</pdfaSchema:property>
</rdf:li>
</rdf:Bag>
</pdfaExtension:schemas>
<fx:DocumentFileName
xmlns:fx=""urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#"">
factur-x.xml
</fx:DocumentFileName>
<fx:DocumentType
xmlns:fx=""urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#"">
INVOICE
</fx:DocumentType>
<fx:Version
xmlns:fx=""urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#"">
1.0
</fx:Version>";
// Post-process: attach XML + inject XMP
DocumentOperation.LoadFile("invoice-base.pdf")
.AddAttachment(new DocumentAttachment
{
FilePath = "factur-x.xml",
Key = "factur-x.xml",
AttachmentName = "factur-x.xml",
MimeType = "text/xml",
Description = "Factur-X XML invoice data",
Relationship = DocumentAttachmentRelationship.Alternative
})
.ExtendMetadata(facturxXmp)
.Save("invoice-zugferd.pdf");
Relationship = Alternative is critical. PDF/A-3 requires every embedded file to declare its relationship to the document. ZUGFeRD / Factur-X mandates /Alternative — the XML is an alternative machine-readable representation of the PDF's visual content. Using any other relationship value will fail validation.
Using PdfEditor (Single-Chain Approach)
If you prefer a single fluent chain from generation through post-processing, use PdfEditor.Create with ExtendXmpMetadata and AddAttachment:
PdfEditor.Create(doc =>
{
doc.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(50);
page.Content().Text("Invoice content here...").FontSize(12);
});
})
.SetTitle("Invoice 2026-0042")
.ExtendXmpMetadata(facturxXmp)
.AddAttachment(new DocumentAttachment
{
FilePath = "factur-x.xml",
Key = "factur-x.xml",
AttachmentName = "factur-x.xml",
MimeType = "text/xml",
Description = "Factur-X XML invoice data",
Relationship = DocumentAttachmentRelationship.Alternative
})
.Save("invoice-zugferd.pdf");
Note: When using PdfEditor.Create, set the PdfAConformance via .WithSettings() on the inner Document.Create call, or generate the base document separately. The PdfEditor post-processing pipeline does not re-encode PDF/A conformance — it preserves whatever conformance the base PDF already has.
The CII XML File
FolioPDF handles the PDF side (embedding, metadata, conformance) but does not generate the CII XML — that is your application's responsibility. A minimal BASIC-profile XML looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice
xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
<rsm:ExchangedDocumentContext>
<ram:GuidelineSpecifiedDocumentContextParameter>
<ram:ID>urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic</ram:ID>
</ram:GuidelineSpecifiedDocumentContextParameter>
</rsm:ExchangedDocumentContext>
<rsm:ExchangedDocument>
<ram:ID>2026-0042</ram:ID>
<ram:TypeCode>380</ram:TypeCode>
<ram:IssueDateTime>
<udt:DateTimeString format="102">20260411</udt:DateTimeString>
</ram:IssueDateTime>
</rsm:ExchangedDocument>
<!-- ... trade transaction, line items, totals ... -->
</rsm:CrossIndustryInvoice>
Consider using a dedicated CII library (e.g. ZUGFeRD-csharp or building from the UN/CEFACT schema) to generate the XML, then embed it with FolioPDF.
XMP Metadata Template
The XMP metadata injected via ExtendMetadata / ExtendXmpMetadata is inserted inside the existing <rdf:Description> tag of the document's XMP stream. The content must include:
| Element | Value | Purpose |
|---|---|---|
fx:DocumentFileName | factur-x.xml | Name of the embedded XML file |
fx:DocumentType | INVOICE | Document type identifier |
fx:Version | 1.0 | Factur-X schema version |
fx:ConformanceLevel | MINIMUM | BASIC WL | BASIC | EN 16931 | EXTENDED | Must match the profile of your CII XML |
pdfaExtension:schemas | (schema definition) | PDF/A extension schema declaring the fx: namespace properties |
ConformanceLevel must match your XML. If the XML is a BASIC profile but the XMP says EN 16931, validators will report a mismatch. Always keep the two in sync.
Validation
Validate the final PDF with veraPDF for PDF/A-3B conformance and the Mustang validator for ZUGFeRD/Factur-X compliance:
# PDF/A-3B validation
verapdf --format mrr --flavour 3b invoice-zugferd.pdf
# ZUGFeRD / Factur-X validation (Mustang)
java -jar Mustang-CLI.jar --action validate invoice-zugferd.pdf
Common validation failures:
| Failure | Cause | Fix |
|---|---|---|
Missing /AFRelationship |
Relationship not set on attachment |
Set Relationship = DocumentAttachmentRelationship.Alternative |
| XMP metadata stream compressed | qpdf recompressed the XMP stream | FolioPDF handles this automatically — when ExtendMetadata is used, stream encoding is preserved |
| Missing Factur-X XMP extension schema | The pdfaExtension:schemas block is missing |
Include the full schema definition in the XMP (see template above) |
| PDF/A-3B stream EOL violation | Missing newline before endstream |
FolioPDF emits newline-before-endstream automatically on save |