Skip to main content

On This Page

Implementing Factur-X: Building Compliant EU E-Invoices from Scratch in TypeScript

3 min read
Share

These articles are AI-generated summaries. Please check the original sources for full details.

Factur-X EN 16931 from scratch: PDF/A-3 + CII XML in Node.js / TypeScript

France’s e-invoicing reform kicks in September 1st, 2026, requiring all B2B invoices to follow structured formats. This implementation provides a production-ready path for Factur-X using under 500 lines of TypeScript without commercial SDKs.

Why This Matters

The technical challenge of Factur-X lies in the synchronization between human-readable PDF/A-3 files and machine-readable CII XML attachments. While ideal models suggest exhaustive schema compliance, real-world production at tevaxia.lu shows that mastering specific business rules like BR-CO-17 rounding and ISO-standard metadata is what prevents rejection by platforms like Chorus Pro and Peppol.

Key Insights

  • Factur-X is a hybrid format where the PDF is the legal document and the CII XML (EN 16931) is the machine-readable twin.
  • The BASIC profile is the recommended SaaS standard, covering 95% of B2B scenarios while avoiding the complexity of the EXTENDED profile.
  • Business Rule BR-CO-17 requires rounding each line item before summation; failing to do so causes validation mismatches in downstream accounting software.
  • PDF/A-3 compliance requires an AFRelationship set to ‘Alternative’, signaling that the XML is a machine-readable version of the PDF content.
  • The format requires strict adherence to UN/ECE Rec 20 unit codes (e.g., ‘C62’ for piece) and ISO 4217 currency codes to pass automated validation.

Working Examples

Core data model for Factur-X invoice structure.

export interface FacturXInvoice {
  profile: "MINIMUM" | "BASIC_WL" | "BASIC" | "EN_16931" | "EXTENDED";
  document_type: "380" | "381" | "384" | "386";
  invoice_number: string;
  issue_date: string;
  currency: string;
  seller: FacturXParty;
  buyer: FacturXParty;
  lines: FacturXLine[];
}

Business logic for calculating totals according to EN 16931 rounding rules.

export function computeTotals(inv: FacturXInvoice): FacturXTotals {
  const lineTotals = inv.lines.map((l) => {
    const gross = l.quantity * l.unit_price_net;
    const discount = l.discount_percent ? gross * (l.discount_percent / 100) : 0;
    return Math.round((gross - discount) * 100) / 100;
  });
  const line_total = lineTotals.reduce((s, v) => s + v, 0);
  // ... VAT grouping and rounding logic
}

Embedding the XML attachment into the PDF with the mandatory AFRelationship metadata using pdf-lib.

await pdf.attach(xmlBytes, "factur-x.xml", {
  mimeType: "application/xml",
  description: "Factur-X (EN 16931 CII)",
  afRelationship: AFRelationship.Alternative,
});

Practical Applications

  • Use Case: Tevaxia.lu generates automated Factur-X invoices for Luxembourg real estate syndic fund calls, ensuring VAT-exempt compliance under Article 261 D CGI.
  • Pitfall: Rounding the final invoice sum instead of individual lines leads to BR-CO-17 validation failures in platforms like Pennylane or Sellsy.
  • Use Case: Hotel PMS systems mapping various USALI categories to specific VAT rates (3% for accommodation vs 17% for F&B) within a single structured file.
  • Pitfall: Relying on Standard14 fonts (Helvetica/Courier) without subsetting fails strict PDF/A-3B veraPDF validation, though it may pass ‘relaxed’ portal checks.

References:

Continue reading

Next article

Secure LLM Agents with Two-Stage Prompt Injection Detection

Related Content