Skip to content

E-invoicing (ZUGFeRD/Factur-X)

ZUGFeRD (Germany) and Factur-X (France) are the same standard: a hybrid e-invoice that is a human-readable PDF and a machine-readable XML invoice in one file. The PDF is a PDF/A-3 document, the invoice data is embedded as factur-x.xml (xrechnung.xml for the XRechnung profile), and XMP metadata declares what is embedded so validators and accounting systems find it.

Papyra produces the PDF side of this completely: the PDF/A-3 container, the embedded file, and the Factur-X XMP extension schema. The invoice XML itself is yours - Papyra embeds it as-is and does not generate or validate invoice data.

byte[] invoiceXml = File.ReadAllBytes("invoice-cii.xml"); // your EN 16931 CII XML
byte[] pdf = document.RenderAsPdf(new PdfRenderOptions
{
Standard = PdfStandard.PdfA3, // required - see below
FacturX = new FacturXOptions(invoiceXml, FacturXProfile.En16931),
});

That single option takes care of everything the standard mandates:

  • the XML is embedded as factur-x.xml (xrechnung.xml for the XRechnung profile, as its spec section mandates) with MIME type text/xml and /AFRelationship /Alternative
  • the file is registered in the PDF’s embedded-files name tree and the catalog’s /AF array (ISO 19005-3)
  • the XMP packet carries the Factur-X properties (fx:DocumentType, fx:DocumentFileName, fx:Version, fx:ConformanceLevel) plus the PDF/A extension schema declaring the fx namespace - without which PDF/A validators reject the file

The profile must match what your XML actually conforms to - it is written into the XMP as fx:ConformanceLevel:

FacturXProfile Conformance level Embedded as Meaning
Minimum MINIMUM factur-x.xml Booking aids only; not a full invoice under EU law
BasicWl BASIC WL factur-x.xml Header/footer data without invoice lines
Basic BASIC factur-x.xml Simple invoices, subset of EN 16931
En16931 EN 16931 factur-x.xml The complete European semantic model (a.k.a. COMFORT)
Extended EXTENDED factur-x.xml EN 16931 plus trade-specific extras
XRechnung XRECHNUNG xrechnung.xml German CIUS for invoices to public authorities

Any file can be embedded, with or without the Factur-X option:

byte[] pdf = document.RenderAsPdf(new PdfRenderOptions
{
Standard = PdfStandard.PdfA3,
Attachments =
[
new PdfAttachment("source-data.csv", csvBytes, "text/csv",
PdfAttachmentRelationship.Data, "Raw figures behind the report"),
],
});

PdfAttachment takes the file name (ASCII, unique per document), the raw bytes, the MIME type, the /AFRelationship (DATA, SOURCE, ALTERNATIVE, SUPPLEMENT, UNSPECIFIED), and an optional description shown by PDF viewers. Attachment streams are Flate-compressed in the file; viewers extract the original bytes.

Standard = PdfStandard.PdfA3 isn’t the only option here - plain PdfStandard.Pdf20 also permits attachments (see Encryption). Reach for PDF/A-3 when the output also needs to be an archival PDF/A file; reach for PDF 2.0 otherwise.

A PDF/A-3 (or PDF 2.0) standard is required

Section titled “A PDF/A-3 (or PDF 2.0) standard is required”

PDF/A-2 (the default) forbids arbitrary embedded files, and Papyra’s PDF/A-4 output has no attachment support (that would be the separate A-4f flavour) - so rendering with Attachments throws unless Standard is PdfStandard.PdfA3 or PdfStandard.Pdf20. FacturX is stricter: its XMP extension schema needs the PDF/A machinery, so it requires PdfStandard.PdfA3 specifically - Pdf20 is not enough.

The Papyra NuGet package ships a Roslyn analyzer that flags the typical mistake at compile time: PAPY101 warns when a PdfRenderOptions initializer sets Attachments without Standard being PdfStandard.PdfA3 or PdfStandard.Pdf20, or sets FacturX without Standard = PdfStandard.PdfA3. The analyzer only sees literal initializers - options flowing through variables or methods are checked at render time.

The repository’s test suite validates the PDF/A-3 conformance of attachment-bearing output (including the Factur-X XMP extension schema) against veraPDF. veraPDF checks PDF/A only - whether the embedded XML is a valid invoice for the declared profile is a separate concern; validate the XML with a Factur-X/ZUGFeRD validator (e.g. Mustang) before embedding it.