Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/eclipxe13/CfdiUtils/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks you through everything you need to issue your first CFDI 4.0 electronic invoice using CfdiUtils. By the end you will have installed the library via Composer, built a Comprobante node with an Emisor, Receptor, and Concepto, computed the totals automatically, digitally signed the document with your private key, validated the result against SAT’s schemas, and exported it as XML — all in a single PHP script.

Install

Add CfdiUtils to your project with Composer:
composer require eclipxe/cfdiutils
Composer will verify that your PHP version is 8.0 or above and that all required extensions (libxml, dom, xsl, simplexml, mbstring, openssl, iconv, json) are present.

Create a CFDI 4.0

1

Load your certificate

Instantiate a CfdiUtils\Certificado\Certificado object by passing the path to your SAT-issued CER file. CfdiUtils will read the serial number, RFC, and certificate contents automatically.
<?php
require __DIR__ . '/vendor/autoload.php';

$certificado = new \CfdiUtils\Certificado\Certificado('/path/to/certificate.cer');
The certificate object is used both to embed the NoCertificado and Certificado attributes in the XML and to verify that your private key matches the certificate during signing.
2

Instantiate CfdiCreator40 with Comprobante attributes

Create a CfdiCreator40 instance, passing an array of Comprobante attributes and your certificate. The constructor automatically sets NoCertificado, Certificado, and — by default — the Emisor RFC and name from the certificate.
$creator = new \CfdiUtils\CfdiCreator40(
    [
        'Serie'               => 'A',
        'Folio'               => '000001',
        'Fecha'               => '2024-01-15T10:00:00',
        'SubTotal'            => '1000.00',
        'Moneda'              => 'MXN',
        'Total'               => '1160.00',
        'TipoDeComprobante'   => 'I',   // I = Ingreso
        'LugarExpedicion'     => '64000',
    ],
    $certificado
);
3

Add Emisor and Receptor

Retrieve the root Comprobante node and add the Emisor and Receptor child nodes. Because the certificate was passed to the constructor, the Rfc and Nombre on Emisor are already populated; you only need to provide RegimenFiscal.
$comprobante = $creator->comprobante();

// RFC and Nombre are already set from the certificate
$comprobante->addEmisor([
    'RegimenFiscal' => '601', // General de Ley Personas Morales
]);

$comprobante->addReceptor([
    'Rfc'                    => 'XAXX010101000',
    'Nombre'                 => 'PUBLICO EN GENERAL',
    'UsoCFDI'                => 'G03',  // Gastos en general
    'DomicilioFiscalReceptor' => '06600',
    'RegimenFiscalReceptor'  => '616',  // Sin obligaciones fiscales
]);
4

Add a Concepto with a tax traslado

Call addConcepto() on the Comprobante node. It returns the new Concepto element, on which you can chain addTraslado() to attach a transferred tax (IVA).
$comprobante->addConcepto([
    'ClaveProdServ'  => '01010101',
    'NoIdentificacion' => 'SKU-001',
    'Cantidad'       => '1',
    'ClaveUnidad'    => 'ACT',
    'Descripcion'    => 'Servicios de desarrollo de software',
    'ValorUnitario'  => '1000.00',
    'Importe'        => '1000.00',
    'ObjetoImp'      => '02', // Sí objeto de impuesto
])->addTraslado([
    'Base'       => '1000.00',
    'Impuesto'   => '002',     // IVA
    'TipoFactor' => 'Tasa',
    'TasaOCuota' => '0.160000',
    'Importe'    => '160.00',
]);
5

Compute totals with addSumasConceptos()

Call addSumasConceptos() to automatically calculate and set SubTotal, Total, Descuento, and the Impuestos node totals based on all Concepto entries. Pass null as the first argument to let the library build the sums object for you.
$creator->addSumasConceptos(null, 2); // second arg = decimal precision
6

Sign with addSello()

Call addSello() with the path to your PEM-encoded private key and its passphrase. The method builds the cadena de origen via XSLT, signs it with SHA-256, and stores the Base64 result in the Sello attribute.
$creator->addSello(
    'file:///path/to/private-key.pem',
    'my_key_passphrase'
);
The file:// URI prefix tells CfdiUtils to read the key from disk. If the key does not belong to the certificate loaded in step 1, a RuntimeException is thrown immediately.
7

Validate the document

Call validate() to run all standard creation validators — XSD schema checks, signature verification, and business-rule assertions. The method returns an Asserts collection you can inspect for errors and warnings.
$asserts = $creator->validate();

if ($asserts->hasErrors()) {
    foreach ($asserts->errors() as $assert) {
        echo $assert->getCode() . ': ' . $assert->getTitle() . PHP_EOL;
        if ('' !== $assert->getExplanation()) {
            echo '  ' . $assert->getExplanation() . PHP_EOL;
        }
    }
    exit(1);
}
8

Export the XML

Use asXml() to get the complete CFDI XML as a string, or saveXml() to write it directly to a file. Before exporting it is recommended to call moveSatDefinitionsToComprobante() to move all namespace declarations to the root node as required by SAT’s technical documentation.
$creator->moveSatDefinitionsToComprobante();

// As a string
$xmlString = $creator->asXml();

// Or save to disk
$creator->saveXml('/path/to/output/cfdi.xml');

Complete Working Example

The following self-contained script combines all eight steps above into a single runnable example.
<?php
require __DIR__ . '/vendor/autoload.php';

// 1. Load the certificate
$certificado = new \CfdiUtils\Certificado\Certificado('/path/to/certificate.cer');

// 2. Create the CfdiCreator40 with Comprobante attributes
$creator = new \CfdiUtils\CfdiCreator40(
    [
        'Serie'             => 'A',
        'Folio'             => '000001',
        'Fecha'             => '2024-01-15T10:00:00',
        'SubTotal'          => '1000.00',
        'Moneda'            => 'MXN',
        'Total'             => '1160.00',
        'TipoDeComprobante' => 'I',
        'LugarExpedicion'   => '64000',
    ],
    $certificado
);

$comprobante = $creator->comprobante();

// 3. Add Emisor and Receptor
$comprobante->addEmisor([
    'RegimenFiscal' => '601',
]);

$comprobante->addReceptor([
    'Rfc'                     => 'XAXX010101000',
    'Nombre'                  => 'PUBLICO EN GENERAL',
    'UsoCFDI'                 => 'G03',
    'DomicilioFiscalReceptor' => '06600',
    'RegimenFiscalReceptor'   => '616',
]);

// 4. Add a Concepto with a traslado (IVA 16%)
$comprobante->addConcepto([
    'ClaveProdServ'    => '01010101',
    'NoIdentificacion' => 'SKU-001',
    'Cantidad'         => '1',
    'ClaveUnidad'      => 'ACT',
    'Descripcion'      => 'Servicios de desarrollo de software',
    'ValorUnitario'    => '1000.00',
    'Importe'          => '1000.00',
    'ObjetoImp'        => '02',
])->addTraslado([
    'Base'       => '1000.00',
    'Impuesto'   => '002',
    'TipoFactor' => 'Tasa',
    'TasaOCuota' => '0.160000',
    'Importe'    => '160.00',
]);

// 5. Compute and write totals
$creator->addSumasConceptos(null, 2);

// 6. Sign with private key
$creator->addSello(
    'file:///path/to/private-key.pem',
    'my_key_passphrase'
);

// 7. Validate
$asserts = $creator->validate();
if ($asserts->hasErrors()) {
    foreach ($asserts->errors() as $assert) {
        echo $assert->getCode() . ': ' . $assert->getTitle() . PHP_EOL;
        if ('' !== $assert->getExplanation()) {
            echo '  ' . $assert->getExplanation() . PHP_EOL;
        }
    }
    exit(1);
}

// 8. Export
$creator->moveSatDefinitionsToComprobante();
$creator->saveXml('/path/to/output/cfdi.xml');

echo 'CFDI created successfully.' . PHP_EOL;

Next Steps

Reading CFDI

Parse and extract data from existing CFDI 3.2, 3.3, and 4.0 XML documents.

Validation Overview

Deep dive into schema validation, signature checks, and custom business-rule validators.

Certificates

Work with SAT CER files, read serial numbers and RFC, and manage certificate chains.

XML Resolver

Configure the local SAT resource cache for XSD and XSLT files.

Build docs developers (and LLMs) love