DEV Community

Olivier EBRAHIM
Olivier EBRAHIM

Posted on

Factur-X 2026: Implementation Guide for SMB Construction

Factur-X 2026: Implementation Guide for SMB Construction

The Silent Revolution in French Construction Invoicing

If you're building software for French construction SMBs, Factur-X 2026 isn't optional anymore—it's law. Starting January 1, 2026, all invoices issued in France must comply with the Factur-X standard (e-invoicing format based on EN 16931). But here's what the regulatory posts won't tell you: it's actually a gift for developers if you understand the mechanics.

I've spent the last year integrating Factur-X into Anodos, a construction management SaaS serving 200+ French SMBs. We started with dread. We ended with competitive advantage. This guide is what I wish existed when we began.

What Factur-X Actually Is (And Isn't)

Factur-X is a dual-format XML-based invoice container. Each invoice contains:

  1. A machine-readable XML payload (conforming to EN 16931)
  2. A human-readable PDF wrapper (the visual invoice your accountant expects)

The hybrid approach sounds redundant, but it's clever: PEPPOL networks (the EU's e-invoicing backbone) validate the XML, your PDF prints normally, and legacy systems that can't parse XML still get a readable document.

Misconception 1: "I need to replace my invoice generator."
Wrong. You're layering XML metadata on top of your existing PDF. If you use a library like reportlab (Python) or pdfkit (Node.js), you're 80% there already.

Misconception 2: "Factur-X requires uploading to a government portal."
False. Your customers' accounting software ingests the files. You send the invoice; they receive it. The government doesn't touch it (except for VAT audits downstream).

Core Technical Components

1. The XML Schema

Your Factur-X XML must validate against the CrossIndustryInvoice (CII) schema. Non-negotiable fields include:

├── DocumentIssueDateTime
├── BillerTradeParty (your SIRET)
├── SellerTradeParty (customer VAT/SIRET)
├── DuePaymentDateTime
├── LineItems (products/services + VAT rates)
└── DocumentTotals (subtotal, VAT, grand total)
Enter fullscreen mode Exit fullscreen mode

Key quirk: VAT rates are mandatory even at 0%. If you're invoicing another EU country, include the VAT code (ZEROnc for reverse-charge, ICMS for Brazilian VAT, etc.).

2. XML Generation Flow

Here's a minimal Python example using xsdata (the de-facto Factur-X library):

from xsdata.formats.dataclass.context import XmlContext
from xsdata.models.datatype import XmlDate
from facturx_schemas import CrossIndustryInvoice, TradeParty, LineItem

def build_facturx_invoice(invoice_data: dict) -> str:
    # Map your invoice object to the CII schema
    trade_invoice = CrossIndustryInvoice(
        issue_date_time=XmlDate.from_date(invoice_data["date"]),
        biller=TradeParty(
            name=invoice_data["issuer_name"],
            siret=invoice_data["issuer_siret"]
        ),
        seller=TradeParty(
            name=invoice_data["customer_name"],
            vat_id=invoice_data["customer_vat"]
        ),
        line_items=[
            LineItem(
                description=item["desc"],
                quantity=item["qty"],
                unit_price=item["price"],
                tax_category_code="S",  # Standard VAT
                tax_rate=20.0
            )
            for item in invoice_data["lines"]
        ]
    )

    # Serialize to XML string
    context = XmlContext()
    return context.build(trade_invoice)
Enter fullscreen mode Exit fullscreen mode

This is intentionally simplified. Real implementations need to handle:

  • Multiple VAT rates per invoice
  • Discounts and surcharges
  • Payment terms and due dates
  • Accounting codes (for French accounting software)

3. Embedding XML into PDF

Once your XML is generated, embed it as an invisible attachment in the PDF:

from PyPDF2 import PdfWriter
from reportlab.pdfgen import canvas
from io import BytesIO

def embed_facturx_in_pdf(pdf_bytes: bytes, xml_string: str) -> bytes:
    pdf_writer = PdfWriter()
    pdf_reader = PdfReader(BytesIO(pdf_bytes))

    # Add all pages from original PDF
    for page in pdf_reader.pages:
        pdf_writer.add_page(page)

    # Embed XML as attachment
    pdf_writer.add_attachment("facturx.xml", xml_string.encode('utf-8'))

    output = BytesIO()
    pdf_writer.write(output)
    return output.getvalue()
Enter fullscreen mode Exit fullscreen mode

Modern PDF readers won't show the attachment to end-users (it's metadata), but accounting software and PEPPOL networks will extract and validate it automatically.

Common Pitfalls We Hit (And You Can Avoid)

Pitfall 1: VAT ID Validation Mismatch

Your customer's VAT ID format matters. EU VAT numbers follow {country_code}{number} (e.g., FR12345678901). If you're invoicing cross-border and paste an invalid VAT, some PEPPOL nodes reject the invoice silently. Always validate VAT IDs against the VIES registry (EC's public API) before embedding.

Pitfall 2: Rounding Errors

Line-level VAT + document-level VAT can differ by €0.01 due to rounding. The spec says line-level rounding is mandatory. Use decimal.Decimal in Python, not floats. One SIRET we worked with had 80+ invoices rejected because their 3-line invoices computed to €0.01 discrepancies.

Pitfall 3: Character Encoding

Factur-X requires UTF-8 strictly. If your customer's name includes accents (common in French: "Société Générale du Bâtiment") and you're passing it as latin-1, the XML serializer will silently truncate or corrupt it. Normalize all input to UTF-8 before binding to schema.

Pitfall 4: Forgetting the PDF/A Wrapper

Factur-X invoices should include a PDF/A-3B visual representation. If you're generating from reportlab, you're not PDF/A-compliant by default. Use a tool like qopdf or ghostscript as a post-processing step to convert to PDF/A-3B (adds ~50ms per invoice).

Testing & Validation

Before sending invoices to production, validate against:

  1. COALA validation portal (CII XML checker): https://coala.ifac.org
  2. Your customer's accounting software (QuickBooks, Sage, Ciel BTP): import 3 test invoices and check for parsing errors
  3. PEPPOL service provider (Chorus Pro in France, if applicable): test through their sandbox

We found that validating locally against the schema (xsdata does this automatically) caught 95% of issues, but COALA caught the 5% that were schema-valid but semantically broken (e.g., invoice amount mismatched payment terms).

Performance & Scaling

At Anodos, we generate 500-1000 invoices per month. Here's what matters:

  • XML generation: ~5ms per invoice (xsdata is fast)
  • PDF embedding: ~50ms (PDF writer overhead dominates)
  • Total latency: ~60ms per invoice (acceptable for async job queues)

If you're generating invoices synchronously in a request handler, you'll timeout. Use a background job queue (Celery, RQ, Bull). Batch invoices by day and generate in the evening when load is low.

What's Next?

Factur-X 2026 compliance is the baseline. The real competitive advantage is in metadata. Smart accounting software (and AI-powered tools) will parse your invoices for:

  • Line-item classification codes (BTP projects have standard code sets)
  • Payment performance (early vs. late payments by customer segment)
  • Margin analysis (which line items are most profitable for your industry)

If you embed structured job site references (e.g., "Project ID: CH-2025-001-Paris-Reno") in your Factur-X metadata, you unlock one-click reconciliation between estimates, invoices, and job sites. That's where the magic happens.

One Last Thing

Factur-X compliance isn't painful if you think of it as structured metadata, not a document reformat. Your invoice remains the same. You're just adding machine-readable annotations that open the door to automation downstream.

The SMBs using Anodos don't think about Factur-X anymore. It's baked into the platform. Your customers shouldn't either—it should be invisible, automatic, and boring.

Build it right once. Then forget about it.


Olivier Ebrahim, founder of Anodos, builds SaaS for French construction SMBs. When not debugging PDF/A compliance, he's obsessed with voice AI for job site workflows.

Top comments (0)