Automation MCP Server Features Blog Pricing Contact
Integration ZUGFeRD Format

horstoeko/zugferd Alternatives: PHP Library vs REST API

horstoeko/zugferd is the strongest open-source e-invoicing library PHP has, and this comparison starts by saying so. What follows is the part the quick-start never shows: a capability matrix against a managed REST API, the totals and PDF/A work the builder leaves with you, the validation wall PHP's XSLT 1.0 ceiling builds into every library stack, and a concrete migration path from the ZugferdDocumentBuilder chain to one HTTP call.

Nobody searches for a horstoeko/zugferd alternative because horstoeko/zugferd is bad software. It is the most capable open-source e-invoicing library in the PHP ecosystem, MIT licensed, actively developed, and the reason a lot of PHP applications ship ZUGFeRD at all. The search usually starts somewhere else: an invoice a recipient rejected that the local pipeline considered fine, a PDF/A-3 merge whose conformance nobody can vouch for, a totals block that drifted out of reconciliation, or the realization that the official validation rules simply cannot run in PHP.

This guide is the full comparison: what the library genuinely does well, a capability matrix against the InvoiceXML ZUGFeRD API, the work the builder model leaves on your side of the line, and a concrete migration path including a mapping from the ZugferdDocumentBuilder chain to the API request model. One disclosure up front: this comparison is written by the maker of one of the alternatives, which is exactly why every horstoeko fact below was checked against the official repository as of September 2026.

If you want the language-specific integration walkthroughs instead, the guides to ZUGFeRD and XRechnung in PHP and Factur-X in PHP cover the full lifecycle with runnable code. This page is about the decision itself.

What horstoeko/zugferd does well

Credit where it is due, because the comparison is meaningless without it:

The strongest builder outside Java. The fluent ZugferdDocumentBuilder writes ZUGFeRD 2.x and Factur-X CII documents across the whole profile ladder, MINIMUM through EXTENDED, XRechnung CII included, and its profile awareness means you do not have to memorize which element exists in which profile. Readers load existing hybrids back into the same world. Nothing else in PHP comes close.

An honest PDF story. The ZugferdDocumentPdfMerger attaches your generated XML to a PDF you supply, building on the established FPDF/FPDI toolchain. It does what it says: it merges. The conformance of the result to PDF/A-3 remains a function of what you feed it, and the project does not pretend otherwise.

A real ecosystem. Sibling packages cover visualization, a UBL bridge, Laravel integration, and Order-X purchase orders, and the author maintains the family actively, with releases arriving steadily. In PHP open source, that consistency is the exception.

Free and unencumbered. MIT, on Packagist, no license conversation between you and a prototype.

So the question this page answers is not whether horstoeko/zugferd is good. It is whether your team should be the one operating everything the library correctly leaves outside its scope, and that question is best answered with the full picture on the table.


Capability matrix

Library capabilities as documented in the official repository as of September 2026; API capabilities link to their documentation.

Capabilityhorstoeko/zugferdInvoiceXML REST API
ZUGFeRD / Factur-X XML generationYes, fluent builder, full profile ladder, CII syntaxYes, JSON in, validated document out (/v1/create/zugferd, /v1/create/facturx)
Totals and VAT breakdownComputed by you and set on the summation blockComputed from the line items server-side, cross-checked if you send your own
Embedding XML into a PDFYes, merger class, your PDF inYes, generated layout or your own PDF
PDF/A-3 conformance of the resultYour responsibility: fonts, ICC profiles, XMP metadata follow from your input PDFHandled server-side; output is validated before it is returned
Reading hybrid PDFs (embedded XML)Yes, reader classesYes, /v1/extract/json and /v1/extract/xml
Reading plain supplier PDFs (no embedded XML)No, structured input requiredYes, AI parsing with confidence scores (/v1/parse/json)
EN 16931 Schematron validationNo in-process route: the rules are XSLT 2.0, PHP's ext-xsl stops at 1.0; XSD validation onlyOfficial rule sets server-side, JSON findings with rule ids and field paths (/v1/validate/zugferd)
KoSIT BR-DE (XRechnung) validationNo, same wall; external Java tooling requiredYes, /v1/validate/xrechnung, both syntaxes auto-detected
XRechnungCII syntax generation; UBL only via the separate bridge packageNative UBL and CII (/v1/create/xrechnung, syntax selected via options.syntax)
Peppol BISNot its scopePeppol BIS 3.0 UBL via /v1/create/ubl, plus EHF, NLCIUS and PINT
Rendering previews of XML invoicesVia the separate visualizer package, template quality yours to own/v1/render/cii/to/pdf and /v1/render/xrechnung/to/pdf
Specification updatesWatch the announcement, wait for the release, bump composer.json, retest, redeployLive server-side with no change on your side
Infrastructure requiredThe package family plus FPDF/FPDI and serializer dependencies in your applicationAny HTTP client; zero compliance dependencies in your build
SupportCommunity, via GitHub issuesProfessional support; missing features integrated on request

Two rows decide most evaluations: validation and specification updates. They are about risk and time, not features: who catches the invoice a recipient will reject, and who does the recurring work when the rules move. The next two sections put detail on both.


What the builder leaves with you

Three responsibilities stay on your side of the library boundary, all by design:

The arithmetic. The builder writes the summation you give it: line totals, tax basis, tax amount, grand total, amount due. EN 16931's calculation rules (the BR-CO family) require all of them to reconcile exactly, including rounding behaviour per VAT group. Getting this right across discounts, charges, multiple VAT categories and exempt lines is precisely the class of bug that ships quietly and surfaces as a recipient-side rejection months later.

The PDF/A-3 layer. The merger attaches XML to your PDF; whether the result is a conformant PDF/A-3 with embedded fonts, ICC colour profile, and correct XMP metadata depends on the PDF you brought. Most PDFs generated by standard PHP tooling are not PDF/A, and the failures are invisible in a viewer: they surface at a Plateforme Agréée, a DATEV import, or an archival audit.

The update calendar. FeRD and FNFE-MPE revise ZUGFeRD and Factur-X, KoSIT revises XRechnung annually, CEN maintains EN 16931 underneath. Each release starts the same loop for a library stack: wait for the package update, bump, retest the pipeline, redeploy every service that touches invoices. The library's maintainer handles his side of this admirably; the loop on your side never ends.

The API's answer to all three is structural: totals and the VAT breakdown are computed from your line items (send your own figures only if you want them cross-checked), the response already is the conformant PDF/A-3, and the current rule sets are live server-side before their effective dates with nothing for you to bump.


The validation wall in PHP

The deeper limit is not the library's; it is the platform's. The official EN 16931 and KoSIT rule sets are Schematron compiled to XSLT 2.0, and PHP's ext-xsl wraps libxslt, which speaks XSLT 1.0 only. There is no Saxon for PHP. The workarounds are all operational: shell out to a Java validator on a JVM you now operate, or send the document to someone who runs the rules for you.

The consequence is that a horstoeko pipeline can check structure (the XSD layer, which the library covers) but not the two hundred plus business rules where real rejections live: BR-CO-15 reconciliation, VAT category rules, the BR-DE contact requirements XRechnung enforces. Those fire at the recipient, whose systems do run the official rules. The library documents this boundary honestly; no PHP package can cross it.

POST /v1/validate/zugferd is the missing layer as one call: the official XSD and Schematron stacks plus PDF/A checks, run server-side, returned as structured JSON findings with rule ids, plain-language messages, and field paths. On the create side the same rules run before any document leaves, so /v1/create/zugferd refuses to return a file that would fail them, and the recipient-side rejection becomes an immediate HTTP 400 with reasons enumerated.


Migrating from horstoeko/zugferd

The switch is a mapping exercise, not a redesign: the builder chain and the API request cover the same semantic ground, and the API computes what the summation block made you compute. Three subsections: the concept mapping, the same invoice written both ways, and the migration sequence.


Mapping the API surface

The table maps the builder calls a typical generation pipeline uses onto the JSON request fields of POST /v1/create/zugferd (the identical model serves /v1/create/facturx and /v1/create/xrechnung):

horstoeko/zugferd conceptInvoiceXML request field or endpoint
ZugferdDocumentBuilder::createNew(ZugferdProfiles::PROFILE_EN16931)The endpoint choice: /v1/create/zugferd, /v1/create/facturx, or /v1/create/xrechnung
setDocumentInformation("RE-2026-001", type, date, currency)invoice.invoiceNumber, invoice.issueDate, invoice.currency
setDocumentSeller(...), setDocumentSellerAddress(...)invoice.seller.name and invoice.seller.postalAddress (line1, postCode, city, country)
addDocumentSellerTaxRegistration("VA", "DE...")invoice.seller.vatIdentifier
setDocumentBuyer(...), setDocumentBuyerAddress(...)invoice.buyer (same shape as the seller)
setDocumentBuyerReference(...) (the Leitweg-ID)invoice.buyerReference
addDocumentPaymentMean(...) with the IBANinvoice.paymentDetails.paymentAccountIdentifier
addNewPosition(...) + product details, price, quantity, taxlines[]: item.name, quantity, unitCode, priceDetails.netPrice, vatInformation.rate
setDocumentSummation(...) with your computed totalsNot needed: totals and the VAT breakdown are computed from the lines
ZugferdDocumentPdfMerger + your PDF/A workNot needed: the create endpoints return a finished PDF/A-3 (or embed into your PDF)
ZugferdDocumentReader on incoming hybridsPOST /v1/extract/json (or /v1/extract/xml for the raw CII)
No equivalentPOST /v1/validate/zugferd, /v1/parse/json, /v1/render/cii/to/pdf

Everything that has no row on the right side is work that disappears rather than moves: the summation arithmetic, the PDF/A conformance project, the validation gap, and the version-tracking habit.


The same invoice, side by side

First the library version, in the builder style its documentation teaches. Note the two blocks the API will make unnecessary: the summation you compute yourself, and the PDF merge whose PDF/A conformance rides on the file you supply:

use horstoeko\zugferd\ZugferdDocumentBuilder;
use horstoeko\zugferd\ZugferdDocumentPdfMerger;
use horstoeko\zugferd\ZugferdProfiles;

$document = ZugferdDocumentBuilder::createNew(ZugferdProfiles::PROFILE_EN16931)
    ->setDocumentInformation("RE-2026-001", "380", new \DateTime(), "EUR")
    ->setDocumentSeller("Mustermann Software GmbH")
    ->setDocumentSellerAddress("Hauptstrasse 12", "", "", "10115", "Berlin", "DE")
    ->addDocumentSellerTaxRegistration("VA", "DE123456789")
    ->setDocumentBuyer("Beispiel Handel AG")
    ->setDocumentBuyerAddress("Marienplatz 8", "", "", "80331", "Muenchen", "DE")
    ->addNewPosition("1")
    ->setDocumentPositionProductDetails("Softwareentwicklung")
    ->setDocumentPositionNetPrice(250.00)
    ->setDocumentPositionQuantity(10, "HUR")
    ->addDocumentPositionTax("S", "VAT", 19)
    // The arithmetic is yours: all six figures must reconcile per BR-CO.
    ->setDocumentSummation(2975.00, 2975.00, 2500.00, 0.0, 0.0, 2500.00, 475.00);

// PDF/A conformance of the result depends on the PDF you bring.
(new ZugferdDocumentPdfMerger($document->getContent(), "invoice-layout.pdf"))
    ->generateDocument()
    ->saveDocument("invoice-zugferd.pdf");

The same invoice as one HTTP call with Guzzle. The API generates the PDF layout, computes totals and the VAT breakdown, produces the conformant PDF/A-3, and validates against the official rule set before returning:

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.invoicexml.com']);

$response = $client->post('/v1/create/zugferd', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('INVOICEXML_API_KEY')],
    'json' => [
        'invoice' => [
            'invoiceNumber' => 'RE-2026-001',
            'issueDate' => '2026-09-29',
            'currency' => 'EUR',
            'seller' => [
                'name' => 'Mustermann Software GmbH',
                'vatIdentifier' => 'DE123456789',
                'postalAddress' => [
                    'line1' => 'Hauptstrasse 12', 'city' => 'Berlin',
                    'postCode' => '10115', 'country' => 'DE',
                ],
            ],
            'buyer' => [
                'name' => 'Beispiel Handel AG',
                'postalAddress' => [
                    'line1' => 'Marienplatz 8', 'city' => 'Muenchen',
                    'postCode' => '80331', 'country' => 'DE',
                ],
            ],
            'lines' => [
                [
                    'quantity' => 10,
                    'unitCode' => 'HUR',
                    'item' => ['name' => 'Softwareentwicklung'],
                    'priceDetails' => ['netPrice' => 250.00],
                    'vatInformation' => ['rate' => 19],
                ],
            ],
        ],
    ],
]);

file_put_contents('invoice-zugferd.pdf', $response->getBody());

The difference is not line count; both snippets are short. The difference is what stands behind them. Behind the first: your arithmetic, your PDF/A conformance, and validation that cannot run on your platform. Behind the second: a finished, validated hybrid, and if the request cannot yield a compliant invoice, an HTTP 400 with the violated rules as structured findings instead of a file. To keep your own PDF layout rather than the generated one, the create endpoints accept it and embed the compliant XML into your design.


The migration path in three steps

Step 1: validate your current output. The opening move costs one HTTP call and no code changes: upload documents your horstoeko pipeline produces today to POST /v1/validate/zugferd. The endpoint accepts files from any producer, extracts the embedded XML, detects the declared profile, and runs the official XSD and Schematron rules plus PDF/A checks, returning findings with rule ids, plain-language messages, and field paths. The report is your baseline: it shows precisely where your current output stands against the current rule sets, summation arithmetic and PDF/A layer included, and therefore exactly what the switch resolves. Wiring the same call into PHPUnit keeps that baseline visible for the rest of the migration.

Step 2: swap generation. Replace the builder chain, the summation block, and the merger with the JSON request from the mapping table. This is the substantive step, and it is smaller than it looks: the field mapping is mechanical, the API computes totals and the VAT breakdown from your line items, and the PDF/A problem disappears because the response already is the finished container. Teams that also serve B2G swap in /v1/create/xrechnung for those buyers with the same request model, and the PHP guide has the full lifecycle code including Laravel queue patterns.

Step 3: remove the dependency family. Drop horstoeko/zugferd and the FPDF/FPDI and serializer stack that rode in with it from composer.json. From this point your invoicing code has zero compliance dependencies and no specification-driven redeploys ahead of it. Incoming documents run through /v1/extract/json for hybrids and /v1/parse/json for the plain supplier PDFs the library never covered, with /v1/extract/attachments unpacking embedded BG-24 documents.

Step 1 is the first move of the migration, not a permanent arrangement: once generation moves in step 2, the same validation endpoint simply becomes your regression check on the API's own output inside CI.


A complete compliance service

What the switch buys, stated as the value proposition it is:

The whole lifecycle behind one integration. Creating ZUGFeRD, Factur-X, XRechnung (both syntaxes), and Peppol BIS UBL from one request model; validating documents from any producer against the official rules; extracting incoming hybrids as JSON; AI parsing for the plain PDFs no library reads; rendering previews; attachment handling. The capability matrix above is not a feature race, but the right column is the complete problem, covered.

The specifications stop being your problem. Current FeRD, KoSIT, and CEN artifacts are live server-side before their effective dates. No release monitoring, no composer bumps, no retest-and-redeploy cycle. Your integration is finished the day it works.

Minimal infrastructure, by design. No PDF toolchain, no Java sidecar for validation, no Schematron artifacts. One HTTP client, which your application already has.

Expertise on call. Deep e-invoicing knowledge with professional support behind the integration, and missing features are integrated on request rather than filed and hoped for.

Processing is stateless throughout: documents are handled in memory and purged when the response ships, nothing is stored or logged, and no invoice data trains any model.


Get started

The fastest way to ground the decision in your own data is step 1 of the migration: validate a few invoices your current pipeline produces and read the report. Create a free InvoiceXML account → and get 100 credits for free, no credit card required.

Runnable PHP examples for every operation live in the examples repository.

Related resources:


InvoiceXML is a REST API for European e-invoice compliance covering ZUGFeRD, Factur-X, XRechnung, Peppol UBL, and CII. Stateless processing, GDPR compliant by architecture, and callable from any stack: PHP, Java, .NET, Node.js, Python, Ruby, or anything else with an HTTP client.

Start free today

Ready to automate your invoices?

Validate, convert and embed compliant e-invoices through one API. Start your 30-day free trial. No credit card required.

GDPR Compliant No credit card required Setup in minutes
Peppol UBL
Factur-X
EN 16931
142 / 142 passed
Compliant
PDF/A-3 embedded