DEV Community

Olivier EBRAHIM
Olivier EBRAHIM

Posted on

Factur-X 2026 Implementation Guide for SMB Construction

Factur-X 2026 Implementation Guide for SMB Construction

Introduction

If you're building software for construction SMBs in France, you've probably heard the term "Factur-X" (or UBL-XML). Starting January 2026, it's no longer optional—it's the law. French companies above a certain threshold must emit all invoices in Factur-X format for B2B transactions. But here's the thing: most developers I talk to find the spec intimidating, and frankly, the documentation isn't developer-friendly.

This guide cuts through the noise. I'll walk you through what Factur-X actually is, why it matters, and how to implement it in your construction management SaaS without losing your mind.

What is Factur-X, Really?

Factur-X is a hybrid invoice format that combines PDF and XML. The PDF is human-readable (your client opens it, sees a pretty invoice). The XML is machine-readable (your client's accounting software parses it automatically). Think of it as a PDF with a built-in JSON API.

The XML payload lives inside the PDF as an attachment. When you emit a Factur-X invoice, you're not replacing your PDF—you're enhancing it.

Why should you care?

  • Compliance: B2B invoices must be Factur-X-compliant by January 2026 (France).
  • Automation: Clients can auto-reconcile your invoice in their accounting software (no data entry).
  • Competitive advantage: Contractors and project managers love SMBs that "just work" with their ERP.

The Technical Stack

A minimal Factur-X implementation needs:

  1. PDF library (e.g., iText, PDFBox, ReportLab)
  2. XML generator (built-in XML libs in your language)
  3. Validation layer (optional but recommended—use the FNFE validator)

Here's a Python example using reportlab + lxml:

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from lxml import etree
import io
from datetime import datetime

def generate_facturx_invoice(invoice_data):
    # Step 1: Create PDF
    pdf_buffer = io.BytesIO()
    c = canvas.Canvas(pdf_buffer, pagesize=A4)

    # Draw invoice (simplified)
    c.setFont("Helvetica", 12)
    c.drawString(50, 750, f"Invoice: {invoice_data['number']}")
    c.drawString(50, 730, f"Amount: €{invoice_data['amount']}")
    c.drawString(50, 710, f"Date: {invoice_data['date']}")

    c.save()

    # Step 2: Generate Factur-X XML
    root = etree.Element("Invoice")

    invoice_id = etree.SubElement(root, "ID")
    invoice_id.text = invoice_data['number']

    issue_date = etree.SubElement(root, "IssueDate")
    issue_date.text = invoice_data['date']

    amount = etree.SubElement(root, "InvoicedAmount")
    amount.text = str(invoice_data['amount'])

    xml_str = etree.tostring(root, xml_declaration=True, encoding='UTF-8')

    # Step 3: Embed XML in PDF (simplified—real implementation uses PyPDF2)
    # In production, use a library like `factur-x` or `zugferd`

    return pdf_buffer.getvalue(), xml_str
Enter fullscreen mode Exit fullscreen mode

In production, use a battle-tested library like:

  • Python: factur-x (FNFE-approved)
  • JavaScript/Node: factur-x-js or roll your own with pdfkit + XML
  • Java: mustangproject (mature, well-maintained)

The XML Schema (Simplified)

A minimal valid Factur-X XML contains:

<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
  <!-- Header -->
  <ExchangedDocumentContext>
    <GuidelineSpecifiedDocumentContextParameter>
      <ID>urn:cen.eu:en16931:2017#conformant#urn:fnfe-fr:billing:3.0.1</ID>
    </GuidelineSpecifiedDocumentContextParameter>
  </ExchangedDocumentContext>

  <!-- Invoice Metadata -->
  <ExchangedDocument>
    <ID>INV-2026-001</ID>
    <IssueDateTime>
      <DateTimeString format="102">20260115</DateTimeString>
    </IssueDateTime>
  </ExchangedDocument>

  <!-- Commercial Transaction -->
  <SupplyChainTradeTransaction>
    <ApplicableHeaderTradeAgreement>
      <!-- Seller details -->
      <SellerTradeParty>
        <Name>My Construction SMB</Name>
      </SellerTradeParty>

      <!-- Buyer details -->
      <BuyerTradeParty>
        <Name>Client Corp</Name>
      </BuyerTradeParty>
    </ApplicableHeaderTradeAgreement>

    <!-- Line items -->
    <IncludedSupplyChainTradeLineItem>
      <SpecifiedLineTradeAgreement>
        <NetPriceProductTradePrice>
          <ChargeAmount>1000.00</ChargeAmount>
        </NetPriceProductTradePrice>
      </SpecifiedLineAgreement>
    </IncludedSupplyChainTradeLineItem>

    <!-- Totals -->
    <ApplicableHeaderTradeSettlement>
      <SpecifiedTradeSettlementHeaderMonetarySummation>
        <DuePayableAmount>1000.00</DuePayableAmount>
      </SpecifiedTradeSettlementHeaderMonetarySummation>
    </ApplicableHeaderTradeSettlement>
  </SupplyChainTradeTransaction>
</Invoice>
Enter fullscreen mode Exit fullscreen mode

Key nodes:

  • GuidelineSpecifiedDocumentContextParameter > ID: declares compliance (must include urn:fnfe-fr:billing:3.0.1 for France)
  • SellerTradeParty, BuyerTradeParty: mandatory parties
  • IncludedSupplyChainTradeLineItem: each line item (material, labor, etc.)
  • ApplicableHeaderTradeSettlement: totals, taxes, payment terms

Common Pitfalls (and How to Avoid Them)

1. Forgetting the French SIRET/SIREN

Factur-X requires business registration numbers. For France, include SIRET (seller) and SIREN/SIRET (buyer).

<SellerTradeParty>
  <Name>My SMB</Name>
  <SpecifiedTaxRegistration>
    <ID schemeID="FR:SIRET">12345678901234</ID>
  </SpecifiedTaxRegistration>
</SellerTradeParty>
Enter fullscreen mode Exit fullscreen mode

2. Wrong Date Format

Factur-X dates are ISO 8601 in format="102" (YYYYMMDD), not YYYY-MM-DD.

<!-- ❌ Wrong -->
<IssueDateTime>2026-01-15</IssueDateTime>

<!-- ✓ Correct -->
<IssueDateTime>
  <DateTimeString format="102">20260115</DateTimeString>
</IssueDateTime>
Enter fullscreen mode Exit fullscreen mode

3. Missing Tax Categories

Every line item must declare its VAT rate. French construction typically uses 20% (standard) or 5.5% (materials in some cases).

<ApplicableProductCharacteristic>
  <Description>VAT-key</Description>
  <Value>S</Value> <!-- S = standard rate (20%) in France -->
</ApplicableProductCharacteristic>
Enter fullscreen mode Exit fullscreen mode

4. Decimal Precision

Amount fields expect 2 decimal places. 1000.0 will fail; use 1000.00.

Testing and Validation

Before deploying to production, validate your XML using the official FNFE validator:

# Upload your invoice to:
# https://fnfe-mvp.mos.minefi.gouv.fr/
Enter fullscreen mode Exit fullscreen mode

Or integrate validation into your CI/CD:

import requests

xml_payload = generate_facturx_xml(invoice_data)

response = requests.post(
    "https://fnfe-mvp.mos.minefi.gouv.fr/api/validate",
    data=xml_payload,
    headers={"Content-Type": "application/xml"}
)

if response.status_code == 200:
    print("✓ Valid Factur-X")
else:
    print(f"✗ Validation failed: {response.text}")
Enter fullscreen mode Exit fullscreen mode

Integration with Your Construction SaaS

If you're building jobsite management software (like Anodos, which handles invoicing for French construction SMBs), here's how Factur-X fits:

  1. Invoice generation step: When a user creates an invoice from a quote, your system generates both PDF + XML.
  2. Email/export: User downloads the Factur-X PDF or sends it directly to the client's email.
  3. Client side: Client's accounting software (Sage, Ciel, QuickBooks FR) auto-imports line items and amounts—no data re-entry.

This cuts invoice reconciliation time from 15 minutes to 30 seconds. Your users will love you.

Resources

Conclusion

Factur-X looks scary on paper, but it's just a standardized XML envelope around your PDF invoice. Once you implement it, you unlock:

  • ✓ Legal compliance in France (and soon, EU-wide)
  • ✓ Automatic invoice reconciliation for your clients
  • ✓ A feature that differentiates your product

Start with a validated Python or Node library, test against the FNFE validator, and you'll be live in a weekend. Your construction SMB clients will appreciate the polish.


Olivier Ebrahim, founder of Anodos — a jobsite management SaaS for French construction teams. We emit Factur-X invoices by default.

Top comments (0)