Introduction
Invoices, delivery notes, debit notes — plenty of teams still produce these by hand, opening an Excel or Word template and typing numbers into the cells one by one. Sound familiar?
The import wholesale operation I worked on was exactly like that. For every transaction, you opened a template, copied over the amounts, and exported a PDF. The more transactions you handle, the more this routine wears you down like body blows.
This article walks through the overall approach we took to break free from that template-based workflow and generate documents mechanically from data instead. The tool of choice was a JavaScript library called pdf-lib.
The Limits of Template-Based Workflows
Tribal knowledge and transcription errors
Template-based workflows are easy to start. But as you keep at it, problems quietly pile up.
The scariest one is transcription errors. You paste the amount into a cell that's off by one, the tax formula quietly breaks, or the recipient's company name is left over from the previous customer. On financial documents, mistakes like these are fatal.
The other is tribal knowledge — the state where "only that one person knows how to touch that file." The moment they take a day off, the work grinds to a halt.
The "sediment" that copy-based workflows create
Another trap of template workflows is that file copies multiply without end.
Files proliferate per transaction. You lose track of which is the latest, and sometimes send an outdated format by accident.
The document is really just data. The format lives in code, so every document comes out at consistent quality.
Keep piling on copies — "let me base this on last month's invoice..." — and you end up with a "sediment" of mismatched format versions. Getting out required a shift in thinking.
The Idea of Generating Documents in Code
Separating "format" from "data"
The heart of this approach is to think of a document as format plus data.
In an Excel template, the format and the data are mixed into a single file. That's exactly why it breaks every time you touch it. With code generation, the format lives in code (a layout definition) and the data lives in a database — kept separate.
To produce a document, you simply combine the two and emit a PDF. Want to change the format? Fix one place in the code, and it applies to every document from then on. That's the decisive difference from a manual workflow.
Rendering with pdf-lib
For the actual generation we used pdf-lib, a library that builds PDFs directly and runs both in the browser and on the server (Node.js).
Prepare transaction data, line items (SKU, quantity, unit price), and recipient info.
Place the title, recipient, line-item table, totals, and bank details by coordinates in code.
pdf-lib assembles the PDF binary.
Save the generated PDF and download or send it as needed.
Since you specify text positions and rules by coordinate, the initial setup takes effort — but once it's built, you only change the data. You can emit hundreds of documents at identical quality.
Making line-item entry efficient
The most laborious part of a document's contents is building the line items — filling in "which product, how many, at what price" row by row.
Here we introduced a SKU lookup cache. Once a product code (SKU) has been looked up, its details are kept on hand, so entering the same SKU again pulls it up instantly without another search. Assembling line items got faster, and it reduced stress for the person doing the entry.
Document Types and the Payoff of Automation
Invoices, delivery notes, and debit notes
In import wholesale, you don't deal with just one kind of document.
Produce these separately by hand and the line items tend to diverge. Generate them from the same data, and the invoice and delivery note automatically match.
The "consistency" that pays off
The obvious payoff of automation is time saved, but the one I felt most strongly was consistency.
What code generation delivers
As long as you generate from the same data source, the numbers across documents are guaranteed to match. The kind of accident that must never happen — "the invoice and delivery note totals disagree" — becomes structurally impossible.
Humans inevitably make mistakes. That's exactly why I believe in shifting toward mechanisms where mistakes simply can't happen.
Conclusion
From manual template work to mechanical generation from data. This shift wiped out, all at once, the chronic pains of document workflows: transcription errors, tribal knowledge, and format drift.
That said, generating documents in code means clearing a few hurdles. Each is explored in its own article, so do read on.
- Embedding Japanese fonts: pdf-lib can't output Japanese out of the box. See Japanese Font Embedding and File-Size Optimization for how to embed fonts while keeping file sizes down.
- Managing recipient and sender profile masters: See Managing Sender and Recipient Profile Masters for a design that avoids retyping company info on every document.
- Storage and reissue: A document isn't done once it's made. See Storing Generated PDFs and Designing Reissue for a mechanism that lets you reproduce the exact same document anytime.
Japanese Font Embedding and File-Size Optimization
How to handle the hardest part of Japanese PDFs — font embedding — reliably and lightly via subsetting.
Managing Sender and Recipient Profile Masters
A design that masters biller and recipient data to keep per-document input minimal.
Storing Generated PDFs and Designing Reissue
Object-storage persistence and a mechanism to reissue the exact same document anytime.