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:
- PDF library (e.g., iText, PDFBox, ReportLab)
- XML generator (built-in XML libs in your language)
- 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
In production, use a battle-tested library like:
-
Python:
factur-x(FNFE-approved) -
JavaScript/Node:
factur-x-jsor roll your own withpdfkit+ 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>
Key nodes:
-
GuidelineSpecifiedDocumentContextParameter > ID: declares compliance (must includeurn:fnfe-fr:billing:3.0.1for 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>
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>
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>
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/
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}")
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:
- Invoice generation step: When a user creates an invoice from a quote, your system generates both PDF + XML.
- Email/export: User downloads the Factur-X PDF or sends it directly to the client's email.
- 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
- FNFE Official Spec (French government source)
- UN/CEFACT Cross-Industry Invoice (technical reference)
- mustangproject (Java) (open-source, battle-tested)
- factur-x (Python) (FNFE-compliant)
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)