Create Factur-X and ZUGFeRD e-invoices
A Factur-X or ZUGFeRD invoice is one file for two readers: a PDF people read, and the same invoice as XML inside it, which accounting software reads instead of the page. Typefold builds the XML from the data you already render the invoice with, checks it against the official rules and embeds it in a PDF/A-3 file.
What Factur-X and ZUGFeRD are
Section titled “What Factur-X and ZUGFeRD are”- One format, two names. ZUGFeRD is the German name and Factur-X the French one. Since ZUGFeRD 2.1 (Factur-X 1.0) they are technically identical.
- The XML is the invoice in the UN/CEFACT Cross Industry Invoice (CII) syntax, following the
European standard EN 16931. It travels as the file
factur-x.xml. - The PDF is a PDF/A-3 file, the archive format that allows embedded files, with an XMP description that names the XML and its profile.
- Profiles set how much the XML holds, from
MINIMUMtoEXTENDED. OnlyBASIC,EN 16931andEXTENDEDare full invoices; see profiles.
Where it is used
Section titled “Where it is used”These rules change, and this page is not legal or tax advice. Check the current rules with your tax adviser or the sources linked here.
- Germany: businesses must be able to receive e-invoices for domestic B2B sales since
1 January 2025. Issuing them is phased in: from 1 January 2027 for businesses whose turnover
in the previous year was more than EUR 800,000, and from 1 January 2028 for all others. An
e-invoice must meet EN 16931: ZUGFeRD from version 2.0.1 qualifies, except the
MINIMUMandBASIC WLprofiles, and so does XRechnung. A plain PDF no longer counts as an e-invoice, and no special network is needed: e-mail works. See the Federal Ministry of Finance’s e-invoice FAQ and the IHK summary of its guidance. - France: all VAT-registered businesses must be able to receive e-invoices from 1 September 2026. Large and mid-sized businesses issue them from that date, small and micro businesses from 1 September 2027, and French invoices gain new required details such as the buyer’s SIREN number. Factur-X is one of the three formats every accredited platform (plateforme agréée) must accept, with UBL and CII. In France, invoices travel through such a platform: Typefold makes the Factur-X file, and your platform sends it. To add the French details, see invoices for France. See Service Public Entreprendre and FNFE-MPE.
- German public authorities often ask for XRechnung, an XML file without a PDF. Typefold renders PDFs, so it does not produce plain XRechnung files.
Start from the invoice starter
Section titled “Start from the invoice starter”The quickest way to a working e-invoice is the Invoice starter: it already embeds an
EN 16931 invoice built from its sample data.
- In Studio, open Editor in the sidebar and choose Add template. Enter a Template name, set Start with to Ready-made starter, pick Invoice and click Create template.
- Change the page and the sample data to fit your invoices, as in get started with the visual editor.
- Click the Factur-X tab in the side panel and check that each field points at your data. The Problems tab lists any rule the sample invoice breaks.
- Click Publish, then render the template with your data like any other, for example with the Reports API.
Every render now returns a PDF/A-3b file with factur-x.xml inside. To add Factur-X to a
template you already have, follow the next section.
Build the XML from your data
Section titled “Build the XML from your data”- Open the invoice template in the visual editor and click the Factur-X tab in its side panel.
- Turn on Embed a Factur-X invoice and pick a profile. EN 16931 suits most invoices.
- Map each field to your data, such as
{{invoice.number}}for the invoice number. The panel suggests the paths the template already shows. Text around a placeholder is kept, and a field without a placeholder is a fixed value, such asEUR. - Under Lines, enter the path of the invoice’s items, such as
invoice.items, and map each line’s fields from an item:{{name}},{{quantity}},{{price}}. - Click Save, then publish the template.
Studio writes the mapping into page.json. In the code editor, write it there yourself:
{ "pdfa": "3b", "facturX": { "profile": "EN 16931", "fields": { "invoiceNumber": "{{invoice.number}}", "issueDate": "{{invoice.date}}", "currency": "EUR", "sellerName": "Example Ltd", "sellerVatId": "DE123456789", "sellerCountry": "DE", "buyerName": "{{invoice.customer.name}}", "buyerCountry": "{{invoice.customer.country}}" }, "lines": { "source": "invoice.items", "fields": { "name": "{{name}}", "quantity": "{{quantity}}", "netPrice": "{{price}}", "vatRate": "{{vat}}" } } }}The API reference lists every field with its EN 16931 business term (BT-1 and
so on). Renders then need nothing more: each one builds the XML from its data, checks it and
embeds it with its XMP description.
- Dates take
2026-10-22, a date and time, or20261022. Numbers take19.5or"19.5". - Fields left empty are left out. The type code is
380(invoice) unless you map it; map381for a credit note. Lines have VAT categoryS, quantity 1 and unitC62(one piece) unless you map them. - Totals and the VAT breakdown are added up from the lines. Map a total to keep your own figure: the check then tells you if it disagrees with the lines.
- An invoice with lines (
BASICand up) can have up to 500 of them. - Without a PDF/A level, the template writes PDF/A-3b. Levels other than
3b,3uand3afail.
Invoices for France
Section titled “Invoices for France”French invoices sent through an accredited platform carry details other invoices leave out.
Map them in page.json beside the other fields:
{ "facturX": { "fields": { "businessProcess": "S1", "sellerLegalId": "{{seller.siren}}", "sellerLegalIdScheme": "0002", "buyerLegalId": "{{buyer.siren}}", "buyerLegalIdScheme": "0002", "latePaymentNote": "Late payment penalty: three times the legal interest rate.", "recoveryFeeNote": "Fixed recovery fee for late payment: EUR 40.", "discountNote": "No discount for early payment." } }}| Field | What it holds |
|---|---|
businessProcess |
The billing framework: B1 for goods, S1 for services, M1 for both. |
sellerLegalId, buyerLegalId |
The SIREN numbers, with sellerLegalIdScheme and buyerLegalIdScheme 0002. |
deliverToName, deliverToStreet, deliverToCity and the other deliverTo fields |
Where goods go, when that is not the buyer’s address. |
vatPointDateCode |
5 when the seller pays VAT on debits. |
latePaymentNote, recoveryFeeNote, discountNote |
The payment notes, written with subject codes PMD, PMT and AAB. |
Use the EN 16931 or EXTENDED profile. Each render checks the EN 16931 rules, not the
French platform rules, so your platform may still reject an invoice they do not cover; its
rules are in the AFNOR XP Z12-012 specification.
Profiles
Section titled “Profiles”The profiles, from smallest to largest:
| Profile | What the XML holds |
|---|---|
MINIMUM |
Header and totals. Not a full invoice under EN 16931. |
BASIC WL |
Header, totals and VAT breakdown, without lines. Not a full invoice. |
BASIC |
A full invoice with lines, limited to the core fields. |
EN 16931 |
The European standard invoice (default). |
EXTENDED |
EN 16931 with room for more detail. |
Use EN 16931 unless your buyer asks for another. In Germany, MINIMUM and BASIC WL do not
count as e-invoices.
How each invoice is checked
Section titled “How each invoice is checked”Every render checks its XML against the profile’s official rules (the EN 16931 rules from CEN,
or the Factur-X rules for MINIMUM and BASIC WL) on the server that renders it, without
sending it anywhere. A render whose XML breaks a rule fails, and the error lists each rule, such as
[BR-06]-An Invoice shall contain the Seller name (BT-27). A value the XML cannot take, such
as a date it cannot read, fails the render with 400 BAD_REQUEST. The visual editor lists both
under Problems, on the block that shows the value.
Embed your own XML
Section titled “Embed your own XML”To build the XML yourself, leave the Factur-X setting off. Set the invoice template to
PDF/A-3b, save it and publish it. Then send the XML file factur-x.xml with each render as
an attachment. The examples render one template version with the
Renders API:
This example uses jq to put the file into the JSON body:
jq -n --rawfile xml factur-x.xml '{ templateVersionId: "0b7d5c1e-8f3a-4d2b-9e61-5a4c3b2a1f00", data: { invoice: { number: "2026-0042", total: 1190.00 } }, waitMs: 10000, attachments: [ { name: "factur-x.xml", content: $xml, mimeType: "text/xml", description: "Factur-X invoice", relationship: "alternative" } ]}' | curl -X POST "$PDF_URL/v1/renders" \ -H "x-api-key: $API_KEY" \ -H "content-type: application/json" \ --data-binary @-using Typefold.Client;
var client = new TypefoldClient( new Uri(Environment.GetEnvironmentVariable("PDF_URL")!), Environment.GetEnvironmentVariable("API_KEY")!);
byte[] pdf = await client.RenderAsync(new TemplateVersionRenderRequest{ TemplateVersionId = "0b7d5c1e-8f3a-4d2b-9e61-5a4c3b2a1f00", Data = new { invoice = new { number = "2026-0042", total = 1190.00 } }, Attachments = new[] { new PdfAttachment { Name = "factur-x.xml", Content = await File.ReadAllTextAsync("factur-x.xml"), MimeType = "text/xml", Description = "Factur-X invoice", Relationship = "alternative", }, },});
await File.WriteAllBytesAsync("invoice.pdf", pdf);import { readFile, writeFile } from "node:fs/promises";import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL!, apiKey: process.env.API_KEY!,});
const pdf = await client.render({ templateVersionId: "0b7d5c1e-8f3a-4d2b-9e61-5a4c3b2a1f00", data: { invoice: { number: "2026-0042", total: 1190.0 } }, attachments: [ { name: "factur-x.xml", content: await readFile("factur-x.xml", "utf8"), mimeType: "text/xml", description: "Factur-X invoice", relationship: "alternative", }, ],});
await writeFile("invoice.pdf", pdf);import { readFile, writeFile } from "node:fs/promises";import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL, apiKey: process.env.API_KEY,});
const pdf = await client.render({ templateVersionId: "0b7d5c1e-8f3a-4d2b-9e61-5a4c3b2a1f00", data: { invoice: { number: "2026-0042", total: 1190.0 } }, attachments: [ { name: "factur-x.xml", content: await readFile("factur-x.xml", "utf8"), mimeType: "text/xml", description: "Factur-X invoice", relationship: "alternative", }, ],});
await writeFile("invoice.pdf", pdf);import osfrom pathlib import Path
from typefold import PdfAttachment, TypefoldClient, TemplateVersionRenderRequest
client = TypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"])
pdf = client.render( TemplateVersionRenderRequest( template_version_id="0b7d5c1e-8f3a-4d2b-9e61-5a4c3b2a1f00", data={"invoice": {"number": "2026-0042", "total": 1190.00}}, attachments=[ PdfAttachment( name="factur-x.xml", content=Path("factur-x.xml").read_text(encoding="utf-8"), mime_type="text/xml", description="Factur-X invoice", relationship="alternative", ) ], ))
Path("invoice.pdf").write_bytes(pdf)- A render can embed up to 10 files, 5 MiB in total. Send text as
contentor bytes ascontentBase64. - In a PDF/A file, every attachment needs
mimeTypeanddescription. Only the levels3b,3u,3aand4fcan embed files.
Factur-X also expects its XMP extension schema. Add it to the template’s page.json, with the
Version and ConformanceLevel of your XML’s profile:
{ "metadata": { "xmp": [ { "name": "Factur-X PDF/A Extension Schema", "prefix": "fx", "namespace": "urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#", "properties": [ { "name": "DocumentType", "value": "INVOICE", "description": "Document type" }, { "name": "DocumentFileName", "value": "factur-x.xml", "description": "Attached XML file" }, { "name": "Version", "value": "1.0", "description": "Factur-X version" }, { "name": "ConformanceLevel", "value": "EN 16931", "description": "Factur-X profile" } ] } ] }}Check a finished file
Section titled “Check a finished file”Open the PDF in a reader that lists attachments to see factur-x.xml. To check a file the way
your buyer’s software will, run it through a Factur-X validator such as
Mustang, and a PDF/A checker such as
veraPDF.
Next steps
Section titled “Next steps”- PDF standards: PDF/A levels, signatures and the standards check.
- Check a template: the Problems tab and the stress test.
- Batch rendering: many invoices in one request.