Factur-X
Generate the EN 16931 CII XML and embed it into a PDF to produce a Factur-X invoice.
@liasse/factur-x produces Factur-X invoices: a human-readable PDF carrying
an embedded, structured CII XML (EN 16931 profile) — the format that becomes
mandatory for French e-invoicing from September 2026.
Pure-TypeScript, and PDF/A-3b valid. The output carries a well-formed
EN 16931 CII, the embedded file, the Factur-X XMP, an sRGB GTS_PDFA1
OutputIntent and a trailer /ID. It validates with veraPDF (0 failed
checks) and against the official EN 16931 CII Schematron (0 errors) —
see the validation/ harness. PDF/A-3b requires the base PDF's fonts to be
embedded: render it with @liasse/pdf using embeddedFonts() (below), or
supply your own theme.fonts. A PDF drawn with non-embedded standard-14 fonts
(e.g. Helvetica) will fail font checks. The Mustang sidecar below remains for
third-party certification and advanced profiles.
A PDF/A-3b-ready base PDF in one line. @liasse/pdf bundles a libre font
(DejaVu LGC Sans); enable embedding via the theme:
import { defineTheme } from "@liasse/core";
import { embeddedFonts } from "@liasse/pdf";
import { facturX } from "@liasse/factur-x";
const theme = defineTheme({ fonts: embeddedFonts() });
const base = await pdfRenderer.render(invoiceDoc, theme);
const facturXPdf = await facturX(invoice, base); // veraPDF: 0 failed checksScope & limitations
Liasse generates Factur-X invoices (PDF/A-3b with an embedded EN 16931 CII) that pass the official EN 16931, EXTENDED-CTC-FR and BR-FR-CTC Schematrons (FNFE-MPE v1.3.1) and veraPDF, with 0 errors.
Covered: service and domestic goods invoices; VAT standard, exempt, reverse charge, zero-rated and export; the mandatory French legal mentions; seller/buyer SIREN/SIRET, electronic addresses and payment means (IBAN).
Not covered (today):
- Transmission — Liasse produces the file; it does not send it. Routing goes through your PDP / the public portal (Chorus Pro).
- Inbound parsing and invoice lifecycle statuses.
- UBL output (CII only).
- Delivery address (BG-15) and intra-community supplies (category K).
- The base PDF must have embedded fonts (use
embeddedFonts()ortheme.fonts); a standard-14 (Helvetica) PDF fails PDF/A-3b font checks.
Conformance is validated against the official Schematrons and tracks the spec version (current target: FNFE-MPE v1.3.1 / EN 16931).
Install
pnpm add @liasse/factur-xDescribe the invoice
import { type Invoice, frLegalNotes } from "@liasse/factur-x";
const invoice: Invoice = {
number: "2026-0412",
issueDate: "2026-09-01",
dueDate: "2026-09-30",
buyerReference: "PO-7788",
// French CTC: the three mandatory legal mentions (PMT/PMD/AAB notes).
notes: frLegalNotes(),
seller: {
name: "ACME SAS",
registrationId: "812449220", // SIREN (9 digits) — BT-30, mandatory in France
vatId: "FR40812449220",
electronicAddress: { value: "facturation@acme.fr", scheme: "EM" }, // BT-34, required
address: { line1: "12 rue des Lilas", city: "Paris", postcode: "75011", countryCode: "FR" },
},
buyer: {
name: "Globex SARL",
registrationId: "81244922000017", // SIRET (14) — Chorus Pro routes on this
registrationScheme: "0009",
vatId: "FR62123456789",
electronicAddress: { value: "compta@globex.fr", scheme: "EM" }, // BT-49, required
address: { city: "Lyon", postcode: "69002", countryCode: "FR" },
},
// Payment means (BG-16) — payee IBAN; often required by Chorus Pro.
payment: { meansCode: "58", iban: "FR7630006000011234567890189", accountName: "ACME SAS" },
lines: [
{ name: "Liasse Pro license", quantity: 5, unitPrice: 490, vatRate: 20 },
{ name: "Training", quantity: 1, unitPrice: 600, vatRate: 10 },
],
};French CTC conformance. The fields above (buyer SIRET, both electronic
addresses, and frLegalNotes()) make the CII pass the official FNFE-MPE
BR-FR-CTC and EXTENDED-CTC-FR Schematrons (v1.3.1) with 0 errors — on
top of EN 16931. frLegalNotes() returns the three legally-required mentions
(recovery costs PMT, late-payment penalties PMD, early-payment discount
AAB); you can also pass your own notes with these subject codes.
Totals, the VAT breakdown by rate, and the grand total are computed for you.
For French B2G/B2B routing, pass the buyer SIRET (registrationScheme: "0009")
and the payment means (IBAN); both flow into the CII and keep it EN 16931-valid.
VAT-exempt lines. For a non-standard VAT category (E exempt, AE reverse
charge, K intra-community, G export, O out of scope), set vatRate: 0 and
a reason — EN 16931 requires it:
{
name: "Prestation exonérée",
quantity: 1,
unitPrice: 1500,
vatRate: 0,
vatCategory: "E",
exemptionReason: "TVA non applicable, article 293 B du CGI", // BT-120
// or exemptionReasonCode: "VATEX-EU-…" // BT-121
}The schema rejects a non-standard category with a non-zero rate or no reason, so
the CII stays Schematron-valid (the BR-E-* / BR-AE-* … rules).
Generate the CII XML
import { generateCII } from "@liasse/factur-x";
const xml = generateCII(invoice); // EN 16931 CrossIndustryInvoiceThe invoice is validated (zod) and a LiasseValidationError-style error is
thrown on bad input.
Produce the Factur-X PDF
Bring your own invoice PDF (e.g. from @liasse/pdf or the invoice component)
and embed the XML:
import { facturX } from "@liasse/factur-x";
import { render } from "@liasse/core";
import "@liasse/pdf";
// 1. render a human-readable invoice PDF (any PDF works)
const pdfBytes = await render(invoiceDoc, { format: "pdf" });
// 2. embed the CII XML → a Factur-X PDF
const facturXPdf = await facturX(invoice, pdfBytes);facturX(invoice, pdf) is shorthand for embedFacturX(pdf, generateCII(invoice)).
The XML is attached as an associated file and the Factur-X XMP metadata is
written so readers recognise it as a Factur-X document.
Conformance API (Mustang sidecar)
For strict conformance — guaranteed PDF/A-3b and the official EN 16931 Schematron validation, the part no JavaScript library fully covers — Liasse runs a JVM sidecar (Mustangproject). It's exposed through two API-key–authenticated endpoints; the TypeScript package generates, the sidecar validates and certifies.
# Validate a Factur-X PDF (or CII XML) → conformance report
curl -X POST https://liasse.tnjl.me/api/facturx/validate \
-H "Authorization: Bearer liasse_sk_xxx" \
--data-binary @facture.pdf
# → { "valid": true, "errors": [], "warnings": [], "reportXml": "…" }# Certify: a visual PDF + its CII XML → a strict PDF/A-3b Factur-X
curl -X POST https://liasse.tnjl.me/api/facturx/certify \
-H "Authorization: Bearer liasse_sk_xxx" -H "content-type: application/json" \
-d '{"pdf":"<base64 PDF>","cii":"<CII XML>"}' \
--output facture-facturx.pdfSame API keys and monthly quota as the render API. The sidecar is self-hosted, so invoice data never leaves your infrastructure.