Automation MCP Server Features Blog Pricing Contact
Integration ZUGFeRD Format

node-zugferd Alternatives: npm Library vs REST API for Node.js Teams

node-zugferd is the most complete ZUGFeRD package on npm, and this comparison starts by giving it that credit. What follows is the part the quick-start does not cover: a capability matrix against a managed REST API, why XSD-only validation leaves the real rejections undetected, what a pre-1.0, work-in-progress dependency means for an invoicing pipeline, and a concrete migration path from invoicer.create() to one fetch call.

Nobody searches for a node-zugferd alternative because node-zugferd is bad software. It is the most complete ZUGFeRD package on npm, it is free under the MIT licence, it is written in TypeScript with types inferred from its profile schemas, and it is downloaded tens of thousands of times a week. The search usually starts somewhere else: an invoice a recipient's validator rejected even though the package's own validation passed, a requirement for XRechnung that no built-in profile covers, incoming hybrids that need reading, or a closer look at the README, which still labels the project work in progress.

This guide is the full comparison: what the library genuinely does well, a capability matrix against the InvoiceXML ZUGFeRD API, the validation gap between an XSD check and the rules recipients actually run, and a concrete migration path including a mapping from the node-zugferd API to the REST request model. One disclosure up front: this comparison is written by the maker of one of the alternatives, which is exactly why every node-zugferd fact below was checked against the official repository and its npm releases as of October 2026.

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

What node-zugferd does well

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

TypeScript first. You create an invoicer for a profile, and the shape of the invoice data is inferred from that profile's schema (typeof invoicer.$Infer.Schema). Your editor knows which fields the profile expects before anything runs, and zod checks the data at runtime. For a Node.js codebase that is the most natural e-invoicing API on npm.

The full profile ladder, and room to extend it. MINIMUM, BASIC WL, BASIC, EN 16931 and EXTENDED ship by default, and the package lets you define your own profiles. Its README is also refreshingly direct about the legal fine print: it warns that MINIMUM and BASIC WL documents do not count as invoices under German law and recommends BASIC at minimum.

Your PDF stays yours. invoice.embedInPdf(pdf, ...) attaches the generated XML to a PDF you already render, using pdf-lib, so teams with an existing invoice layout can add the ZUGFeRD layer without touching their design.

Free, light and widely used. MIT licensed, a small dependency tree (pdf-lib, fast-xml-parser, zod, defu), and heavy weekly download numbers. That matters, and an honest comparison says so.

So the question this page answers is not whether node-zugferd is good. It is whether the piece it covers is the piece your compliance problem actually consists of, and that question is best answered with the full picture on the table.


Capability matrix

Library capabilities as documented in the official repository and npm releases as of October 2026; API capabilities link to their documentation.

Capabilitynode-zugferdInvoiceXML REST API
ZUGFeRD / Factur-X XML generationYes, CII syntax, profiles MINIMUM to EXTENDED plus custom profilesYes, JSON in, validated document out (/v1/create/zugferd, /v1/create/facturx)
Hybrid PDFAttaches the XML to a PDF you supply (embedInPdf); conformance of the result depends on that input PDFGenerates a finished PDF/A-3, or embeds into your own PDF (options.pdfUrl, /v1/embed/zugferd), checked before it is returned
ValidationXSD only, through the optional xsd-schema-validator package, which needs Java on the machine; returns valid or invalidXSD plus the official EN 16931 Schematron, JSON findings with rule ids and field paths (/v1/validate/zugferd)
KoSIT BR-DE (XRechnung) validationNoYes, /v1/validate/xrechnung, both syntaxes auto-detected
XRechnungNo built-in profileNative UBL and CII (/v1/create/xrechnung, syntax selected via options.syntax)
Reading ZUGFeRD and Factur-X invoicesListed as coming soonYes, /v1/extract/json and /v1/extract/xml
Reading plain supplier PDFs (no embedded XML)NoYes, AI parsing with confidence scores (/v1/parse/json)
Peppol BISNot its scopePeppol BIS 3.0 UBL via /v1/create/ubl, plus EHF, NLCIUS and PINT
Rendering previews of XML invoicesNo/v1/render/cii/to/pdf and /v1/render/xrechnung/to/pdf
Specification updatesWait for a package release, bump, retest, redeployLive server-side with no change on your side
Project statusLabelled work in progress; latest release 0.1.1-beta.1 (August 2025)Production API with versioned endpoints
SupportCommunity, via GitHub issuesProfessional support; missing features integrated on request

Two rows decide most evaluations, and neither is about generation. Validation and project status are about risk: who catches the invoice that would be rejected, and who carries the work while the dependency is still taking shape. The next two sections take them in turn.


The validation gap in Node.js

node-zugferd's validation step checks the generated XML against the profile's XSD. That catches malformed XML: a missing mandatory element, a wrong data type, an element in the wrong place. It is useful, and it is not where real rejections come from. The two hundred plus business rules of EN 16931 live in Schematron: totals that do not reconcile per BR-CO-15, a VAT category whose required exemption reason is missing, an allowance without its reason code. A document can pass the XSD and fail every one of them, and the recipient's system, which does run the official rules, is where you find out.

Two details make the gap wider in practice. The XSD check depends on the optional xsd-schema-validator package, which performs the validation with Java and expects java and javac on the path, so a Node.js service quietly gains a JVM requirement. And the official Schematron artifacts are published as XSLT 2.0; running them in Node.js means compiling them for SaxonJS yourself and recompiling every time FeRD, CEN or KoSIT publish a new release. Most teams decline, and rightly so.

The API's answer is to run the machinery where it can run: POST /v1/validate/zugferd executes the official XSD and Schematron stacks plus the hybrid checks server-side and returns findings as structured JSON with rule ids, plain-language messages, and field paths. And on the create side the same rules run before any document leaves: /v1/create/zugferd refuses to return a file that would fail them, converting a recipient-side rejection into an immediate HTTP 400 with the reasons enumerated.


A work-in-progress dependency

European e-invoicing specifications move on a schedule your roadmap does not control: FeRD and FNFE-MPE revise ZUGFeRD and Factur-X, KoSIT revises XRechnung annually, CEN maintains the EN 16931 artifacts underneath. For a library-based stack, every publication starts the same loop: watch the announcement, wait for the package release that supports it, bump, retest the pipeline, and redeploy every service that touches invoices.

For node-zugferd that loop starts from an earlier point. The README carries a clear caution that the package is still under development, every published version so far is below 1.0, the latest release (0.1.1-beta.1) dates from August 2025, and reading existing invoices is announced as coming soon. That is a transparent status for an open-source project maintained in the open, and teams evaluating today should price it in: pre-1.0 releases can change the API between versions, the features you need next may not have a release date, and the gap between the package and the current specifications is yours to watch.

The API removes the loop rather than relocating it. Current FeRD, KoSIT, and CEN artifacts are applied server-side before their effective dates; your integration does not change, and nothing on your side needs monitoring, bumping, or redeploying. The team whose full-time job is e-invoicing compliance absorbs the treadmill so yours does not.


Migrating from node-zugferd

The switch is smaller than most teams expect, because the API's request model covers the same semantic ground as a profile schema, and the API computes totals and the VAT breakdown from line items, so the migration is a mapping exercise, not a redesign. Three subsections: the concept mapping, the same invoice written both ways, and the migration sequence.


Mapping the API surface

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

node-zugferd conceptInvoiceXML request field or endpoint
zugferd({ profile: BASIC })The endpoint choice; EN 16931 is the default profile, and invoice.specificationId (BT-24) selects another official profile
The invoice data passed to invoicer.create(data)The invoice object: invoiceNumber, issueDate, currency, seller, buyer, lines[]
Totals and VAT breakdown you compute and pass inComputed by the API from lines[]
invoice.toXML()POST /v1/create/cii for the XML alone
invoice.embedInPdf(pdf, ...) with your own PDFoptions.pdfUrl on the create call, or POST /v1/embed/zugferd with your PDF and XML
No PDF of your ownPOST /v1/create/zugferd renders the PDF (with options.logoUrl, brandColor and language)
Profile validate (XSD, Java required)POST /v1/validate/zugferd (XSD and Schematron), and the same checks run on every create call
Custom profile for XRechnungPOST /v1/create/xrechnung, UBL or CII
No equivalent yetPOST /v1/extract/json, /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 totals arithmetic, the Java requirement, the validation gap, and the version-tracking habit.


The same invoice, side by side

First the library version, following the pattern its README teaches. The invoice data itself depends on the profile schema, so it is left as a comment here; note what the code leaves open afterwards:

import fs from "node:fs";
import { zugferd } from "node-zugferd";
import { BASIC } from "node-zugferd/profile/basic";

const invoicer = zugferd({ profile: BASIC });

const data = {
  // Seller, buyer, lines, totals and VAT breakdown, shaped by the
  // BASIC profile schema (typeof invoicer.$Infer.Schema).
};

const invoice = invoicer.create(data);

// The PDF must show exactly the data passed above.
const pdf = fs.readFileSync("./invoice.pdf");
const hybrid = await invoice.embedInPdf(pdf, {
  metadata: { title: "RE-2026-001" },
});
// Still ahead: Schematron validation, PDF/A conformance of your input,
// and every specification release from here on.

The same invoice as one HTTP call. 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:

import fs from "node:fs/promises";

const payload = {
  invoice: {
    invoiceNumber: "RE-2026-001",
    issueDate: "2026-10-01",
    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.0 },
        vatInformation: { rate: 19 }
      }
    ]
  }
};

const response = await fetch("https://api.invoicexml.com/v1/create/zugferd", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.INVOICEXML_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify(payload)
});

if (!response.ok) throw new Error(await response.text());
await fs.writeFile("invoice-zugferd.pdf", Buffer.from(await response.arrayBuffer()));

The difference is not line count; both snippets are short. The difference is what stands behind them. Behind the first: an XML attachment on a PDF you are responsible for, checked against the schema at most. 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, pass it as options.pdfUrl and the API embeds 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 hybrids your node-zugferd pipeline produces today to POST /v1/validate/zugferd. The endpoint accepts files from any producer, extracts the XML, detects the declared profile, and runs the official XSD and Schematron rules plus the hybrid 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 and therefore exactly what the switch resolves. Wiring the same call into Jest or Vitest keeps that baseline visible for the rest of the migration.

Step 2: swap generation. Replace invoicer.create() and embedInPdf() 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 container work disappears because the response already is the finished hybrid. If your product renders its own invoice PDF, keep it and pass it as options.pdfUrl. Teams that also serve B2G swap in /v1/create/xrechnung for those buyers with the same request model, and the Node.js guide has the full lifecycle code including serverless patterns.

Step 3: remove the package. Drop node-zugferd, pdf-lib and xsd-schema-validator from your dependencies, and the Java runtime from your images if nothing else needs it. 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, 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 version bumps, no retest-and-redeploy cycle, and no dependency on any package's roadmap. Your integration is finished the day it works.

Minimal infrastructure, by design. No PDF library, no Java runtime for validation, no XSLT compiler, no Schematron artifacts. One fetch call, which Node.js already ships.

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 Node.js 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: Node.js, .NET, Java, Python, PHP, 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