@stackforge-eu/factur-x
v1.1.0
Published
Embed Factur-X, ZUGFeRD, and XRechnung structured invoice XML into PDF/A-3 documents
Readme
Embed Structured Invoices into PDF
Generate and embed Factur-X, ZUGFeRD, and XRechnung compliant CII XML into PDF/A-3 invoices.
This TypeScript/Node.js package takes a simple, typed invoice input object and:
- Validates it against profile requirements
- Generates UN/CEFACT CII XML (Factur-X 1.08 / ZUGFeRD 2.4)
- Validates the XML against official XSD schemas (via WASM)
- Embeds it into a PDF with PDF/A-3b metadata
- Or outputs standalone XRechnung XML for German B2G use
- Extracts existing XML from Factur-X/ZUGFeRD PDFs
Features
| Feature | Status | | ------------------------------------------------------ | ------ | | Profiles: MINIMUM, BASIC WL, BASIC, EN 16931, EXTENDED | ✅ | | Flavors: Factur-X, ZUGFeRD, XRechnung, Chrono Pro | ✅ | | Profile-aware input validation | ✅ | | CII XML generation from typed input | ✅ | | XSD schema validation (libxml2-wasm) | ✅ | | PDF/A-3b embedding with XMP metadata | ✅ | | Standalone XRechnung CII XML output | ✅ | | Extract XML from existing Factur-X/ZUGFeRD PDF | ✅ |
Installation
Node.js
npm install @stackforge-eu/factur-xDeno
import { embedFacturX, Profile } from "jsr:@stackforge-eu/factur-x";Profiles
| Profile | Line Items | VAT Breakdown | Payment | Use Case | | ------------ | :--------: | :-----------: | :-----: | ---------------------- | | MINIMUM | — | — | — | Minimal OCR-level data | | BASIC WL | — | ✅ | ✅ | Document-level B2B | | BASIC | ✅ | ✅ | ✅ | Standard commercial | | EN 16931 | ✅ | ✅ | ✅ | Full EU standard | | EXTENDED | ✅ | ✅ | ✅ | Complex scenarios |
Flavors
| Flavor | Region | Output | Notes |
| ------------------- | ----------- | -------- | ----------------------------- |
| Flavor.FACTUR_X | France / EU | PDF/A-3 | Default, Factur-X 1.08 |
| Flavor.ZUGFERD | Germany | PDF/A-3 | ZUGFeRD 2.4 (= Factur-X 1.08) |
| Flavor.XRECHNUNG | Germany B2G | XML only | PEPPOL business process |
| Flavor.CHRONO_PRO | Belgium | PDF/A-3 | Chrono Pro conventions |
Quick Start
Embed Factur-X into an existing PDF
import {
embedFacturX,
DocumentTypeCode,
UnitCode,
VatCategoryCode,
Profile,
Flavor,
} from "@stackforge-eu/factur-x";
import { readFile, writeFile } from "fs/promises";
const pdfBuffer = await readFile("invoice.pdf");
const result = await embedFacturX({
pdf: pdfBuffer,
input: {
document: {
id: "INV-2025-001",
issueDate: "2025-03-01",
typeCode: DocumentTypeCode.COMMERCIAL_INVOICE,
},
seller: {
name: "StackForge UG (haftungsbeschränkt)",
address: { line1: "Bergstraße 4", city: "Weihmichl", postalCode: "84107", country: "DE" },
taxRegistrations: [{ id: "DE123456789", schemeId: "VA" }],
},
buyer: {
name: "Kite-Engineer by Stefan Merthan",
address: { line1: "Hauptstraße 6", city: "Weihmichl", postalCode: "84107", country: "DE" },
},
lines: [
{
id: "1",
name: "Consulting Service",
quantity: 10,
unitCode: UnitCode.HOUR,
unitPrice: 150,
vatCategoryCode: VatCategoryCode.STANDARD_RATE,
vatRatePercent: 19,
},
],
totals: {
lineTotal: 1500,
taxBasisTotal: 1500,
taxTotal: 285,
grandTotal: 1785,
duePayableAmount: 1785,
currency: "EUR",
},
vatBreakdown: [
{
categoryCode: VatCategoryCode.STANDARD_RATE,
ratePercent: 19,
taxableAmount: 1500,
taxAmount: 285,
},
],
payment: {
meansCode: "58",
iban: "DE89370400440532013000",
dueDate: "2025-03-31",
},
},
profile: Profile.EN16931,
flavor: Flavor.ZUGFERD,
});
await writeFile("invoice-zugferd.pdf", result.pdf);Generate XRechnung XML (no PDF)
import { toXRechnung, VatCategoryCode } from "@stackforge-eu/factur-x";
const { xml } = toXRechnung({
document: {
id: "INV-2025-001",
issueDate: "2025-03-01",
buyerReference: "04011000-12345-67", // Leitweg-ID
},
seller: {
name: "StackForge UG (haftungsbeschränkt)",
address: { line1: "Bergstraße 4", city: "Weihmichl", postalCode: "84107", country: "DE" },
taxRegistrations: [{ id: "DE123456789" }],
},
buyer: {
name: "Kite-Engineer by Stefan Merthan",
address: { line1: "Hauptstraße 6", city: "Weihmichl", postalCode: "84107", country: "DE" },
},
lines: [
{
id: "1",
name: "Service",
quantity: 1,
unitPrice: 1000,
vatCategoryCode: VatCategoryCode.STANDARD_RATE,
vatRatePercent: 19,
},
],
totals: {
lineTotal: 1000,
taxBasisTotal: 1000,
taxTotal: 190,
grandTotal: 1190,
duePayableAmount: 1190,
currency: "EUR",
},
vatBreakdown: [
{
categoryCode: VatCategoryCode.STANDARD_RATE,
ratePercent: 19,
taxableAmount: 1000,
taxAmount: 190,
},
],
});
// Upload `xml` to ZRE / OZG-RE / PEPPOLExtract XML from an existing PDF
import { extractXml } from "@stackforge-eu/factur-x";
import { readFile } from "fs/promises";
const pdf = await readFile("invoice-with-xml.pdf");
const { xml, filename, profile } = await extractXml(pdf);
console.log(`Found ${filename} (profile: ${profile})`);
console.log(xml);Validate Against XSD Schema
import { validateXsd, buildXml, Profile } from "@stackforge-eu/factur-x";
const xml = buildXml(invoiceData, Profile.EN16931);
const result = await validateXsd(xml, Profile.EN16931);
if (!result.valid) {
console.error("XSD errors:", result.errors);
}Validate Input Without Embedding
import { validateInput, Profile } from "@stackforge-eu/factur-x";
const result = validateInput(invoiceData, Profile.EN16931);
if (!result.valid) {
console.error("Validation errors:", result.errors);
}Build XML Only
import { buildXml, Profile, Flavor } from "@stackforge-eu/factur-x";
const xml = buildXml(invoiceData, Profile.BASIC_WL, Flavor.FACTUR_X);API Reference
embedFacturX(options): Promise<EmbedResult>
Embeds Factur-X XML into a PDF with PDF/A-3b metadata. Optionally runs XSD validation with validateXsd: true.
extractXml(pdf, options?): Promise<ExtractResult>
Extracts embedded Factur-X / ZUGFeRD XML from an existing PDF. Returns the XML string, filename, and detected profile.
toXRechnung(input, options?): XRechnungResult
Generates standalone XRechnung CII XML.
buildXml(input, profile, flavor?): string
Builds CII XML from input without embedding.
validateInput(input, profile): ValidationResult
Validates input against profile requirements.
validateXsd(xml, profile, options?): Promise<XsdValidationResult>
Validates XML against the official Factur-X XSD schema using libxml2-wasm.
getFlavorConfig(flavor): FlavorConfig
Returns configuration for a flavor.
See the full type definitions for all interfaces.
Development
npm ci # Install dependencies
npm run validate # Type-check with tsc
npm run lint # Lint with ESLint
npm run test -- --run # Run vitest suite
npm run test:deno # Run Deno compatibility tests (requires Deno >= 2.0)
npm run build # Build with tsup (CJS + ESM)License
EUPL-1.2 — European Union Public Licence, version 1.2
© 2026 - StackForge UG (haftungsbeschränkt)
See LICENSE for details.
