DEV Community

Olivier EBRAHIM
Olivier EBRAHIM

Posted on

Factur-X 2026 for Developers: Implementing E-Invoice in 2 Hours (Really)

Factur-X 2026 for Developers: Implementing E-Invoice in 2 Hours (Really)

By 2026, all French SME invoices must be Factur-X compliant by law. If you're building for construction (BTP), you're looking at ~70% of your user base requiring this by April 2026. Here's how to ship it without the usual XML nightmare.

What is Factur-X?

Factur-X (EN 16931 XML + PDF hybrid) is France's mandated e-invoice format. It's not just XML in a folder—it's XML embedded inside a PDF. When you email an invoice, it looks like a PDF to humans. To machines, it's structured data.

Why this matters for your code: You can't slap XML on a PDF. You need:

  1. A valid PDF/A-3 (not regular PDF)
  2. XML conforming to CII (Cross Industry Invoice) schema
  3. An attachment relationship in the PDF metadata
  4. MIME type application/vnd.openxmlformats-officedocument.spreadsheetml.sheet (yep, that's a thing)

The 2-Hour Implementation Path

Step 1: Grab a Library (30 mins)

Don't write PDF manipulation yourself. Use:

  • Python: facturx (PyPI, pip install facturx). French-first, used by 1000+ production systems.
  • Node.js: factur-x npm package. Solid, but less mature than Python.
  • PHP: setasign/SetaPDF-Signer or pure XML generation + tcpdf (pain, avoid if possible).
  • Java: Apache PDFBox + custom XML schema. Overkill for SME flows.
pip install facturx
Enter fullscreen mode Exit fullscreen mode

Step 2: Generate the Invoice XML (45 mins)

You need to map your invoice data → CII schema. The schema has ~200 fields. You'll use ~12.

Minimal invoice XML (CII format):

<?xml version="1.0" encoding="UTF-8"?>
<CrossIndustryInvoice xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
  <ExchangedDocumentContext>
    <GuidelineSpecifiedDocumentContextParameter>
      <ID>urn:cen.eu:en16931:2017</ID>
    </GuidelineSpecifiedDocumentContextParameter>
  </ExchangedDocumentContext>

  <ExchangedDocument>
    <ID>DEVIS-2026-001</ID>
    <TypeCode>380</TypeCode> <!-- 380 = invoice, 381 = credit note -->
    <IssueDateTime><DateTimeString format="102">20260415</DateTimeString></IssueDateTime>
    <IncludedNote>
      <Content>Devis de plâtrerie cuisine</Content>
    </IncludedNote>
  </ExchangedDocument>

  <SupplyChainTradeTransaction>
    <!-- Seller (your PM) -->
    <ApplicableHeaderTradeAgreement>
      <SellerTradeParty>
        <ID>FR12345678901</ID>
        <Name>ACME Plâtrerie SARL</Name>
        <DefinedTradeContact>
          <PersonName>Jean Dupont</PersonName>
          <TelephoneUniversalCommunication><CompleteNumber>+33612345678</CompleteNumber></TelephoneUniversalCommunication>
        </DefinedTradeContact>
        <PostalTradeAddress>
          <LineOne>123 Rue de la Paix</LineOne>
          <CityName>Lyon</CityName>
          <PostcodeCode>69000</PostcodeCode>
          <CountryID>FR</CountryID>
        </PostalTradeAddress>
        <SpecifiedTaxRegistration>
          <ID schemeID="VA">FR12345678901</ID>
        </SpecifiedTaxRegistration>
      </SellerTradeParty>

      <!-- Buyer (the client) -->
      <BuyerTradeParty>
        <Name>Maçonnerie Martin EIRL</Name>
        <PostalTradeAddress>
          <LineOne>456 Ave de l'Industrie</LineOne>
          <CityName>Villeurbanne</CityName>
          <PostcodeCode>69100</PostcodeCode>
          <CountryID>FR</CountryID>
        </PostalTradeAddress>
      </BuyerTradeParty>
    </ApplicableHeaderTradeAgreement>

    <!-- Payment & totals -->
    <ApplicableHeaderTradeSettlement>
      <InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
      <ApplicableTradeTax>
        <CalculatedAmount>240.00</CalculatedAmount>
        <TypeCode>VAT</TypeCode>
        <BasisAmount>1200.00</BasisAmount>
        <RateApplicablePercent>20</RateApplicablePercent>
      </ApplicableTradeTax>
      <SpecifiedTradeSettlementHeaderMonetarySummation>
        <LineTotalAmount>1200.00</LineTotalAmount>
        <TaxBasisTotalAmount>1200.00</TaxBasisTotalAmount>
        <TaxTotalAmount currencyID="EUR">240.00</TaxTotalAmount>
        <GrandTotalAmount>1440.00</GrandTotalAmount>
      </SpecifiedTradeSettlementHeaderMonetarySummation>
    </ApplicableHeaderTradeSettlement>

    <!-- Line items -->
    <IncludedSupplyChainTradeLineItem>
      <AssociatedDocumentLineDocument>
        <LineID>1</LineID>
      </AssociatedDocumentLineDocument>
      <SpecifiedTradeProduct>
        <Description>Plâtrerie salle de bain (plafond + 2 cloisons)</Description>
      </SpecifiedTradeProduct>
      <SpecifiedLineTradeAgreement>
        <NetPriceProductTradePrice>
          <ChargeAmount>1200.00</ChargeAmount>
        </NetPriceProductTradePrice>
      </SpecifiedLineTradeAgreement>
      <SpecifiedLineTradeSettlement>
        <ApplicableTradeTax>
          <TypeCode>VAT</TypeCode>
          <RateApplicablePercent>20</RateApplicablePercent>
        </ApplicableTradeTax>
        <SpecifiedTradeSettlementLineMonetarySummation>
          <LineTotalAmount>1200.00</LineTotalAmount>
        </SpecifiedTradeSettlementLineMonetarySummation>
      </SpecifiedLineTradeSettlement>
    </IncludedSupplyChainTradeLineItem>
  </SupplyChainTradeTransaction>
</CrossIndustryInvoice>
Enter fullscreen mode Exit fullscreen mode

This is the core. You can generate this with templating (Jinja2/EJS) or a library like xml.etree.ElementTree (Python).

Step 3: Embed into PDF (30 mins)

from facturx import make_facturx

# 1. Create your PDF first (reportlab, weasyprint, whatever)
pdf_bytes = generate_pdf_with_reportlab(invoice_data)

# 2. Embed Factur-X XML
facturx_bytes = make_facturx(
    pdf_bytes,
    xml_string,
    facturx_version="2p0"  # Factur-X 2.0 = newest standard
)

# 3. Save to file or email
with open("devis-2026-001-facturation.pdf", "wb") as f:
    f.write(facturx_bytes)
Enter fullscreen mode Exit fullscreen mode

That's it. facturx handles PDF/A-3 conversion, metadata injection, and schema validation.

Step 4: Validate Before Sending (15 mins)

In production, ALWAYS validate before hitting Send:

from facturx import get_facturx_xml_from_pdf

# Load the file you just created
with open("devis-2026-001-facturation.pdf", "rb") as f:
    extracted_xml = get_facturx_xml_from_pdf(f)

if extracted_xml:
    print("✓ Factur-X embedded correctly")
else:
    print("✗ Not a valid Factur-X PDF")
Enter fullscreen mode Exit fullscreen mode

Common Pitfalls

1. "I forgot the VAT registration (SIRET / SIREN)"

Factur-X REQUIRES:

  • Seller SIRET (14 digits) or SIREN (9 digits) with VAT number
  • If missing, your invoice is legally invalid in France

Check before embed:

if not seller_siret or len(seller_siret) not in [9, 14]:
    raise ValueError("Invalid SIRET/SIREN")
Enter fullscreen mode Exit fullscreen mode

2. "I'm embedding the wrong version"

Factur-X has 3 versions: 1.0 (legacy), 2.0 (current), 2p0 (2.0 with profiles). Use 2p0 for 2026 compliance.

3. "PDF won't open in Outlook"

If you generated a pure PDF/A-3, some older Outlook versions choke. Solution: test with make_facturx(pdf_bytes, xml_string, facturx_version="2p0", profile="CIUS"). The CIUS profile is more compatible with Outlook + Sage/Ciel accounting software.

4. "Quantity and unit are confusing the schema"

CII schema expects BilledQuantity + CalculatedAmount. Don't mix Quantity (CII term) with LineQuantity (different spec). Stick to official CII schema examples.

Integration with Anodos or Similar PM Software

If you're building a BTP SaaS (like Anodos, which generates 400+ invoices/month across its PME network), integrate Factur-X at the quote → invoice transition:

  1. User generates quote (voice input, AI-assisted)
  2. Client approves quote
  3. User creates invoice from quote
  4. System auto-generates Factur-X PDF (no user input needed)
  5. Email goes out with embedded invoice
  6. Accounting software auto-imports VAT, line items, totals

This 2-min integration saves your users 15 mins of manual accounting entry per invoice. For a 50-user PM, that's 125 hours/month.

Testing Checklist

  • [ ] Generate invoice with 0%, 5.5%, 20% VAT lines (all rates)
  • [ ] Test with missing optional fields (description, contact info)
  • [ ] Validate XML against official CII XSD schema
  • [ ] Open resulting PDF in Adobe Reader, Outlook, Firefox (3 clients minimum)
  • [ ] Extract XML with get_facturx_xml_from_pdf and verify round-trip
  • [ ] Email to your accountant, ask them to import into their software
  • [ ] Test with 2+ year-old invoice (historical data migration)

Ship It

You've got 30 months until April 2026. If you ship Factur-X support now (2 hours of dev time), you'll be ahead of 90% of your competition. Your users will thank you.


Olivier Ebrahim, fondateur d'Anodos — logiciel de gestion chantier avec facturation Factur-X 2026 intégrée pour PME BTP.

Top comments (0)