Document generation at scale: dynamic PDFs, Word and PowerPoint from an ecommerce pipeline
- automation
- documents
- ecommerce
In nearly every ecommerce or B2B project we see, sooner or later the same request arrives: “Can we generate the contract/the delivery note/the report automatically?”. The technical answer is trivial — yes, you can. The useful answer is something else: with what architecture, because otherwise you end up with six different scripts generating six types of document, each with its own bug and no shared source of truth on branding.
In this article I lay out how we structure a document generation pipeline that handles six different document types — payout statements, contracts, delivery notes, condition reports with AI-generated text, technical docs snapshots and sales brochures — all powered by a single template engine. The example comes from a project in furniture resale, but the pattern applies to any ecommerce or B2B pipeline.
Why you need a template engine, not six scripts
The most common mistake is starting with a Puppeteer script for the first PDF, then a wkhtmltopdf script for the second because it needed a different feature, then a library for PowerPoint because “we need slides for sales pitches”. Six months in you have six toolchains, each with its own dependencies, and every logo change requires six pull requests.
The approach that works is a single template engine producing different outputs from the same data source. In our stack: a Supabase Edge Function per document type, but they all call the same rendering layer. The rendering layer takes { template_id, data, output_format } and returns the binary. Templates are HTML+CSS versioned in the repo, with shared partials for headers, footers, price blocks, tables.
Sounds obvious when stated like that. It isn’t when you’re under pressure to ship the first feature next week.
The six document types
In the reference project, the pipeline generates:
1. Settlement PDF — payout statement for the seller. Sales summary for the period, fees, taxes, total paid. Generated on-demand when the seller opens their portal, or automatically on the eligible → paid state transition.
2. Agreement PDF — consignment contract signed during onboarding. Seller data + clauses + applied commission rates. The template has a clauses section that varies by jurisdiction (IT/UK/DE) while keeping the same layout.
3. Collection PDF — pickup instructions for the logistics partner. Address, contact, photos of the pieces, access notes. Generated when the listing state moves to ready_for_collection.
4. Delivery Note — shipping note. Customer details, item breakdown, delivery conditions. Generated on transition to shipped.
5. Condition Report — QC report with photos plus descriptive text generated by Gemini. This is the most interesting case: the flow runs through AI (condition description from photos + QC notes), then a “voice gate” verifies the text matches the tone of voice before injecting it into the PDF template.
6. Tech Docs Snapshot — admin technical documentation. Snapshot of configurations, database schema, list of edge functions. Generated on-demand by admins for internal audits. Output as PDF and Markdown.
For other projects we add sales brochures in PPTX or DOCX format — useful when the sales team wants to personalize slides after generation.
The pattern: data layer → template engine → format adapter
The architecture that has held up well is three layers:
Data layer. A SQL view or RPC that, given the entity ID (settlement, order, listing), returns a denormalized JSON with all the data needed for the document. It’s the only source of truth: if the invoice shows a total, it comes from here. Benefit: if a calculation rule changes, it changes in one place.
Template engine. Takes { template_id, data } and returns pre-rendered HTML. We use Handlebars templates with shared partials. Templates live in the repo, versioned like code. Every template change is a PR with automatic preview.
Format adapter. Transforms the HTML into PDF (Puppeteer + headless Chromium), DOCX or PPTX (pandoc or dedicated converters). The adapter layer is the only thing that changes per format; template and data stay the same.
A detail that makes a real difference: PDF rendering runs in a dedicated Edge Function with a pool of pre-initialized headless browsers, because Chromium cold start costs 2-3 seconds. With the pool, P95 settlement PDF generation stays under 800ms.
AI inside the template: the Condition Report case
The Condition Report is the only document where AI writes text that ends up in a PDF signed by the QC team. It’s also the riskiest case: off-brand or incorrect text prints with the same authority as everything else. The pipeline is:
- Input: photos of the piece + structured notes from the QC operator (scratches, material integrity, surface condition).
- Gemini receives a prompt with few-shot examples of acceptable descriptions and produces a draft.
- Voice gate: an eval function checks banned words, tone score (against brand criteria), and regression against a test set of known outputs.
- Only if the voice gate passes does the text enter the template. Otherwise it goes back to manual review.
The voice gate is what lets you sleep at night. Without it, a single prompt drift can inject wrong text into 200 documents before anyone notices.
Tech docs snapshot: documents generated from the code
The last type deserves a separate note. For complex projects — when you have 100+ edge functions, multiple RLS policies, scheduled cron jobs — hand-written technical documentation is stale the moment you publish it. The snapshot works like this: an Edge Function reads metadata from Postgres (pg_cron.job, pg_proc, pg_policy), introspects deployed edge functions, and generates a PDF/Markdown that’s an exact photograph of the system.
We use it for security audits and onboarding new developers. The fact that it’s regenerated on demand — and therefore always current — removes the “stale docs” category from the backlog.
A realistic metric
On a mid-volume project (around 1,500 documents/month across payouts, delivery notes and contracts), this architecture holds these numbers:
- P95 PDF generation time: 650-800ms
- Time to refactor after a logo/brand change: one PR on a shared partial, propagated to all six types.
- Infrastructure cost per document generated: in the range of a few cents per month for the volumes mentioned.
Nothing magical. The thing that makes the difference isn’t performance — it’s that six months in you keep adding new document types without multiplying technical debt.
When to start with this
If you’re about to generate your second type of dynamic PDF, it’s the moment to stop and think about the shared template engine. If you’re already at four different scripts, the refactor is painful but pays off in three months. If you generate a single document and don’t foresee more, inline Puppeteer is fine — don’t over-engineer.
The question to ask isn’t “what library do I use”, but “in six months how many document types will I have, and how much does it cost me to change the logo?”. The answer tells you whether you need the architecture above or not.