Générer un PDF en PHP, tout le monde sait faire. Générer une Factur-X conforme, c'est autre chose : il faut produire le bon XML (CII), l'embarquer dans un PDF/A-3 et y injecter les métadonnées XMP attendues. Rater une de ces trois étapes, et la facture est techniquement invalide — même si le PDF s'affiche parfaitement.

Bonne nouvelle : une librairie gère la partie la plus délicate. Voici le chemin complet, du XML au fichier validé.

🔎 À la fin, vérifiez toujours votre fichier dans le Testeur de factures Factur-X — c'est le réflexe qui évite les rejets en production.

Le principe : deux couches, un seul fichier

facture.pdf  (Factur-X)
├── 📄 Couche visuelle  → un PDF/A-3 lisible
└── 🤖 Couche données    → factur-x.xml (CII) embarqué

Vous fournissez deux choses : le PDF visuel de votre facture (celui que vous générez déjà) et le XML Factur-X. La librairie assemble les deux en un PDF/A-3 conforme. Pour le contexte complet du format, voir le guide Factur-X : comprendre le format.

Étape 1 — Installer la librairie

La référence en PHP est atgp/factur-x (PHP 7.4+) :

composer require atgp/factur-x

Elle s'appuie sur setasign/fpdf, setasign/fpdi et smalot/pdfparser, et sait générer, extraire et valider le XML embarqué.

Étape 2 — Construire le XML Factur-X (CII)

C'est votre responsabilité : la librairie n'invente pas vos données. Voici un squelette profil MINIMUM (le plus simple — sans lignes de détail) à adapter :

<?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice
    xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
    xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
    xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
  <rsm:ExchangedDocumentContext>
    <ram:GuidelineSpecifiedDocumentContextParameter>
      <ram:ID>urn:factur-x.eu:1p0:minimum</ram:ID>
    </ram:GuidelineSpecifiedDocumentContextParameter>
  </rsm:ExchangedDocumentContext>
  <rsm:ExchangedDocument>
    <ram:ID>FA-2026-0042</ram:ID>
    <ram:TypeCode>380</ram:TypeCode>
    <ram:IssueDateTime>
      <udt:DateTimeString format="102">20260627</udt:DateTimeString>
    </ram:IssueDateTime>
  </rsm:ExchangedDocument>
  <rsm:SupplyChainTradeTransaction>
    <ram:ApplicableHeaderTradeAgreement>
      <ram:SellerTradeParty>
        <ram:Name>Ma Société SARL</ram:Name>
        <ram:SpecifiedLegalOrganization>
          <ram:ID schemeID="0002">123456789</ram:ID>
        </ram:SpecifiedLegalOrganization>
      </ram:SellerTradeParty>
      <ram:BuyerTradeParty>
        <ram:Name>Client SAS</ram:Name>
      </ram:BuyerTradeParty>
    </ram:ApplicableHeaderTradeAgreement>
    <ram:ApplicableHeaderTradeDelivery/>
    <ram:ApplicableHeaderTradeSettlement>
      <ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
      <ram:SpecifiedTradeSettlementHeaderMonetarySummation>
        <ram:TaxBasisTotalAmount>1000.00</ram:TaxBasisTotalAmount>
        <ram:TaxTotalAmount currencyID="EUR">200.00</ram:TaxTotalAmount>
        <ram:GrandTotalAmount>1200.00</ram:GrandTotalAmount>
        <ram:DuePayableAmount>1200.00</ram:DuePayableAmount>
      </ram:SpecifiedTradeSettlementHeaderMonetarySummation>
    </ram:ApplicableHeaderTradeSettlement>
  </rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>

Points de vigilance : la date est au format 102 (AAAAMMJJ), la devise suit ISO 4217, et TaxBasis + TaxTotal = GrandTotal (sinon rejet — voir les 20 erreurs). Pour un profil EN 16931 (COMFORT) avec les lignes de détail, le XML est plus riche ; on l'assemble souvent par gabarit ou via une librairie d'écriture dédiée plutôt qu'à la main.

Étape 3 — Embarquer le XML dans le PDF/A-3

C'est là que atgp/factur-x fait le travail difficile (PDF/A-3 + XMP) :

<?php
require 'vendor/autoload.php';

use Atgp\FacturX\Facturx;

$pdfVisuel = file_get_contents('facture.pdf');   // votre PDF existant
$xml       = file_get_contents('factur-x.xml');  // le XML de l'étape 2

$facturx = new Facturx();

// Assemble un PDF/A-3 conforme contenant le XML embarqué
$facturxPdf = $facturx->generateFacturxFromFiles($pdfVisuel, $xml);

file_put_contents('facture-facturx.pdf', $facturxPdf);

La librairie nomme automatiquement la pièce jointe factur-x.xml, pose la bonne relation de fichier et écrit les métadonnées XMP — les erreurs n°1 à 4 de la checklist sont ainsi éliminées d'office.

Étape 4 — Valider le résultat

Ne jamais mettre en production sans valider. D'abord le schéma, en local :

$isValid = $facturx->checkFacturxXsd($xml); // true / exception si non conforme

Puis une validation complète (schéma + règles métier EN 16931 + cohérence PDF) :

🔎 Test rapide — le Testeur Factur-X de SynapxLab : présence du XML, PDF/A-3, profil, montants, mentions. Gratuit, aucune donnée conservée.

🏛️ Validation faisant autorité — le validateur du FNFE-MPE, avant toute mise en production.

Lire / extraire le XML d'une Factur-X existante

L'opération inverse est tout aussi simple :

$pdf = file_get_contents('facture-facturx.pdf');
$xml = $facturx->getFacturxXmlFromPdf($pdf);   // récupère le XML embarqué

À détailler dans un prochain article du cluster : comment lire et exploiter le XML embarqué.

Et après ? Générer ≠ transmettre

Produire un fichier conforme n'est qu'une étape. La réforme impose ensuite de le transmettre via une plateforme agréée (PA), de gérer les statuts (reçue, acceptée, encaissée) et l'e-reporting. Une librairie traite le format ; elle ne fait ni la transmission, ni les statuts, ni l'e-reporting.

💡 Pour un flux complet sans tout recoder, un ERP/CRM génère la Factur-X et la transmet via une PA, avec suivi du cycle de vie. Le cadre complet est détaillé dans notre guide de la réforme.


❓ FAQ

Quelle librairie PHP pour générer une Factur-X ? atgp/factur-x : elle embarque le XML dans un PDF/A-3 conforme, et sait aussi l'extraire et le valider.

La librairie génère-t-elle le XML à ma place ? Non : vous fournissez le PDF et le XML (CII). La librairie gère l'embarquement conforme et les métadonnées XMP.

Comment vérifier que ma Factur-X est valide ? checkFacturxXsd() en local pour le schéma, puis le testeur Factur-X ou le FNFE-MPE pour une validation complète.

Ai-je besoin de Ghostscript ? Pas pour l'embarquement de base. Mais normaliser des PDF d'origines variées en PDF/A-3 strict peut nécessiter un outil de conversion ou de contrôle (Ghostscript, veraPDF).

Pour aller plus loin