Motivation
node-zugferd currently covers two of the three steps needed to produce a complete e-invoice:
- ✅ Generate Factur-X/ZUGFeRD XML from typed data (
invoice.toXML())
- ❌ Produce the visual PDF itself
- ✅ Embed the XML and apply PDF/A-3b conformance (
invoice.embedInPdf(pdf))
For step 2, users must either bring their own PDF or set up @node-zugferd/api, which requires an HTTP server, a self-written template, and Puppeteer (a full Chromium download — slow cold starts and problematic on serverless platforms).
I'd like to contribute a new plugin package that closes this gap with a single programmatic call — no server, no Chromium — including a built-in, customizable default template whose primary goal is a legally compliant German invoice (all mandatory content of § 14 Abs. 4 UStG, DIN 5008-inspired layout).
Proposed API
import { zugferd } from "node-zugferd";
import { EN16931 } from "node-zugferd/profile/en16931";
import { pdf } from "@node-zugferd/pdf";
import { defaultTemplate } from "@node-zugferd/pdf/templates";
export const invoicer = zugferd({
profile: EN16931,
plugins: [
pdf({
template: defaultTemplate({
logo: fs.readFileSync("./logo.png"),
accentColor: "#1e3a5f",
locale: "de",
footer: {
columns: [
"Lieferant GmbH\nLieferantenstraße 20\n80333 München",
"Geschäftsführer: Hans Muster\nAmtsgericht München HRB 12345",
"IBAN: DE02 1203 0000 0000 2020 51",
],
},
}),
}),
],
});
// data → rendered PDF → embedded XML → PDF/A-3b, in one call:
const pdfA = await invoicer.createPdf(data, { metadata: { title: "Rechnung 471102" } });
The plugin registers createPdf through the existing ZugferdPlugin mechanism, so it fits the current architecture without core changes.
How it works
data
└─▶ template(ctx) → pdfmake TDocumentDefinitions
└─▶ pdfmake printer → PDF buffer (fonts embedded)
└─▶ document.create(data).embedInPdf(buffer, opts)
→ Uint8Array (validated XML embedded, PDF/A-3b metadata applied)
Rendering is done with pdfmake (pure JS, MIT): declarative document definitions, automatic page breaks for long line-item tables, no native modules.
Key design points
- Germany-first default template: renders all § 14 Abs. 4 UStG mandatory content (seller/buyer, VAT ID / tax number, invoice number, dates, line items, per-rate VAT breakdown incl. exemption reasons, totals, payment terms). Rendered exclusively from the same data that produces the XML, so the visual PDF can never contradict the embedded XML.
- Three customization tiers: simple options (logo, colors,
de/en labels, footer) → override individual sections (header, line table, totals, …) → replace the template entirely (any function returning a pdfmake docDefinition).
- PDF/A-safe by construction: fonts are always embedded (pdfmake's non-embedded standard-14 fonts path is not used), so
embedInPdf's PDF/A-3b post-processing stays valid.
- Profile support: the default template requires BASIC or higher (matching the README's guidance that MINIMUM/BASIC WL aren't valid invoices under German law); custom templates are unrestricted.
- Quality: Vitest tests next to sources (formatters, template content against the existing test fixtures, full-pipeline integration), best-effort veraPDF conformance check, docs page under
docs/content/docs/plugins/ mirroring the API plugin docs.
- Non-goal: replacing
@node-zugferd/api — HTML/CSS templates via Puppeteer remain the right choice for pixel-perfect corporate designs. This plugin optimizes for zero-setup DX.
Open questions
- Package name:
@node-zugferd/pdf or @node-zugferd/template?
- Is
pdfmake acceptable as a dependency (pure JS, MIT)? The alternative — hand-rolled layout on top of the existing pdf-lib dependency — avoids the dep but means a lot more code (line breaking, table pagination, …).
- Handler name
createPdf ok?
- Bundled default font: reuse Roboto (already inside pdfmake) or Liberation Sans?
- Longer term, should the template contract live somewhere shared so
@node-zugferd/api could consume the same templates?
Offer
I'd like to implement this myself and submit it as a PR (package, tests, and docs included). I have a detailed design document ready and I'm happy to adjust any of the above to your preferences before starting.
Motivation
node-zugferd currently covers two of the three steps needed to produce a complete e-invoice:
invoice.toXML())invoice.embedInPdf(pdf))For step 2, users must either bring their own PDF or set up
@node-zugferd/api, which requires an HTTP server, a self-written template, and Puppeteer (a full Chromium download — slow cold starts and problematic on serverless platforms).I'd like to contribute a new plugin package that closes this gap with a single programmatic call — no server, no Chromium — including a built-in, customizable default template whose primary goal is a legally compliant German invoice (all mandatory content of § 14 Abs. 4 UStG, DIN 5008-inspired layout).
Proposed API
The plugin registers
createPdfthrough the existingZugferdPluginmechanism, so it fits the current architecture without core changes.How it works
Rendering is done with pdfmake (pure JS, MIT): declarative document definitions, automatic page breaks for long line-item tables, no native modules.
Key design points
de/enlabels, footer) → override individual sections (header, line table, totals, …) → replace the template entirely (any function returning a pdfmake docDefinition).embedInPdf's PDF/A-3b post-processing stays valid.docs/content/docs/plugins/mirroring the API plugin docs.@node-zugferd/api— HTML/CSS templates via Puppeteer remain the right choice for pixel-perfect corporate designs. This plugin optimizes for zero-setup DX.Open questions
@node-zugferd/pdfor@node-zugferd/template?pdfmakeacceptable as a dependency (pure JS, MIT)? The alternative — hand-rolled layout on top of the existing pdf-lib dependency — avoids the dep but means a lot more code (line breaking, table pagination, …).createPdfok?@node-zugferd/apicould consume the same templates?Offer
I'd like to implement this myself and submit it as a PR (package, tests, and docs included). I have a detailed design document ready and I'm happy to adjust any of the above to your preferences before starting.