CfdiUtils provides a comprehensive validation system that checks your CFDI documents at multiple layers. When you validate a CFDI, the library doesn’t simply tell you whether it passed or failed — it runs every registered rule and records a result for each one, whether that result is a success, a failure, a warning, or a check that could not be performed. This granular reporting lets you pinpoint exactly which rules are satisfied and which are not, making debugging and compliance auditing significantly easier. The validation pipeline works in three layers: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.
- Schema validation — the document is checked against the official SAT XSD schemas declared in
xsi:schemaLocation. If the document fails schema validation, further validation stops because the data cannot be trusted. - XML structure checks — for CFDI 4.0, additional rules verify the correct namespace, root node name, and version number.
- Business rule validation — 80+ SAT-defined rules covering monetary precision, RFC formats, tax totals, digital seals, Timbre Fiscal Digital integrity, and more.
Validation Architecture
The validation system is built from four cooperating classes in theCfdiUtils\Validate namespace.
MultiValidator
MultiValidator is the orchestrator. It holds an ordered list of ValidatorInterface objects and executes them one by one against the cfdi:Comprobante node. Each validator writes its results into the shared Asserts collection. If any validator sets the mustStop flag, the loop ends immediately and no subsequent validators run.
You normally obtain a properly configured MultiValidator through MultiValidatorFactory rather than building one by hand.
MultiValidatorFactory
MultiValidatorFactory creates pre-configured MultiValidator instances for each supported CFDI version and use case. It uses a Discoverer to automatically load all validator classes found in the Cfdi33/Standard, Cfdi33/RecepcionPagos, and Cfdi40/Standard directories.
| Factory method | Purpose |
|---|---|
newCreated33() | Validators for a CFDI 3.3 document being created |
newReceived33() | Validators for a CFDI 3.3 document received (same set as created) |
newCreated40() | Validators for a CFDI 4.0 document being created |
newReceived40() | Validators for a CFDI 4.0 document received (same set as created) |
Assert
An Assert represents a single validation rule result. It is phrased as an affirmative statement — for example, “The CFDI has a defined currency that belongs to the currency catalogue” — not as an error message. This makes every result self-documenting regardless of whether it passed or failed.
Each Assert carries four properties:
| Property | Accessor | Description |
|---|---|---|
code | getCode() | Short identifier such as MONDEC01 |
title | getTitle() | Human-readable description of the rule |
status | getStatus() | The outcome: OK, ERROR, WARN, or NONE |
explanation | getExplanation() | Optional detail about why the rule failed or was skipped |
Asserts
Asserts is a keyed collection of Assert objects. Each code is unique within the collection — if a validator writes a result for a code that already exists, the previous entry is overwritten. The collection is iterable, countable, and provides helper methods to filter by status.
Validation Statuses
EveryAssert carries a Status value object. Status is immutable — once created, its value cannot be changed. The four possible values are:
| Status | String value | Meaning |
|---|---|---|
Status::ok() | OK | The rule was checked and the document passed. |
Status::error() | ERROR | The rule was checked and the document failed. The CFDI is invalid and should be rejected. |
Status::warn() | WARN | A potential issue was found, but it is unclear whether the CFDI should be rejected. Human review is recommended. |
Status::none() | NONE | The rule could not be evaluated (e.g., a required complemento was absent, or the currency is not one of the supported ones). |
NONE does not mean the document is invalid. It means the specific condition required for that rule to apply was not met. For example, decimal-precision rules for MXN, USD, EUR, and XXX return NONE for any other currency because the SAT does not define decimal limits for it.Validating a Created CFDI
When you build a CFDI usingCfdiCreator33 or CfdiCreator40, the creator object already holds all the data needed for validation. You can call validate() directly on the creator instance — it internally constructs a MultiValidator using MultiValidatorFactory and runs it against the node tree you have built.
CfdiCreator40 with CfdiCreator33.
Validating a Received CFDI
For CFDIs received from a third party or downloaded from the SAT portal, useCfdiValidator40 or CfdiValidator33. Pass the raw XML string to validateXml().
Working with Asserts
After validation you have a fullAsserts collection. You can iterate every result, filter by status, or look up a specific rule by its code.
The mustStop Behavior
The XmlFollowSchema validator is special: if the XML document does not conform to its declared XSD schemas, the validator sets the mustStop flag on the Asserts collection. When MultiValidator detects this flag after running a validator, it immediately aborts the remaining validators.
This behavior exists because all subsequent business-rule validators rely on the assumption that the document structure is valid. If the schema check fails, the data cannot be trusted and running further checks would produce meaningless or misleading results.
See Also
Standard Validation Rules
Reference for all CFDI 3.3 standard validators: XSD schema, decimal precision, impuestos, sello digital, and more.
CFDI 4.0 Validation Rules
CFDI 4.0-specific validators, differences from 3.3, and new validation codes.