Sooner or later every business application has to produce a document. A quote the customer can forward to their boss, an invoice that accounting will accept, a report the board reads on a tablet, a certificate that has to look official. The first version is usually the browser’s print dialog pointed at a web page, and it’s fine until a customer asks why the quote has the navigation menu on it and the table splits across pages mid-row. Doing this properly is a well-understood problem with a few standard approaches, and the choice between them depends on how much control you need over the layout and how many documents you produce.
Quick answer: Render an HTML template to PDF with a headless browser (Playwright or Puppeteer) for most cases; it gives you full control over layout using CSS you already know. Use a dedicated PDF service when you’d rather not run a browser on your server, and a Word-template approach when non-developers need to edit the layout. Whatever you use, store the PDF you sent, with a version number, rather than regenerating it later, because the data behind a quote changes and the quote shouldn’t.
What are the options?
HTML to PDF with a headless browser. Your application builds an HTML page from a template, a headless Chrome instance renders it, and you get a PDF that looks the way the page looked. Playwright and Puppeteer are the usual tools. The strengths are that developers already know HTML and CSS, the layout can be as sophisticated as a web page, and it’s free. The costs are that a browser is a heavy thing to run on a server (memory, startup time, an occasional crash to handle) and that some print-specific features, page numbers in footers in particular, take some work.
PDF libraries that draw directly. Tools like PDFKit, ReportLab, or iText build the document element by element in code. Precise, fast, and no browser needed, at the cost of writing layout code instead of templates. Suited to simple, high-volume documents like labels or receipts, and tedious for anything designed.
Hosted PDF services (DocRaptor, PDFMonkey, API2PDF, and others). You send HTML or a template plus data, they return a PDF. Nothing to run, good support for print CSS, priced per document. Worth it when volume is modest and nobody wants to operate a browser in production.
Document templates. A Word or Google Docs template with placeholders, filled from data and converted to PDF. The advantage is that the office manager can change the letterhead without a developer. The disadvantage is that Word’s layout engine and PDF conversion are less predictable than HTML, and table-heavy documents get awkward. Best for letters and contracts, worst for reports with data tables.
Legacy tools like wkhtmltopdf still turn up in older systems. They work, they render with a browser engine from over a decade ago, and they’re unmaintained; a system that relies on one is due a migration.
What goes wrong?
Fonts. The server doesn’t have the font the designer used, so the PDF falls back to something else and the layout shifts. Ship the fonts with the application and reference them explicitly in the template.
Page breaks. A table row split across two pages, a heading stranded at the bottom, a signature block on its own page. Print CSS has properties for all of this (page-break-inside, break-after, and their newer equivalents), and they need to be applied deliberately to every element that shouldn’t split. Test with a document long enough to break in the awkward places.
Headers, footers, and page numbers. “Page 3 of 7” and the company details on every page are easy in Word and require either the browser’s header template feature or CSS paged media in HTML. Both work; both take a developer an afternoon the first time.
Tables that span pages. Repeating the header row on each page, keeping totals with the rows they total, and not letting a long description cell split mid-sentence. Again solvable, again needs testing with real data rather than three sample rows.
Images. Logos at screen resolution look soft in print. Use vector formats (SVG) for logos and high-resolution images sized for print, and watch the file size on documents with many photos.
Locale. Currency symbols, date formats, and number separators for the customer’s country rather than the server’s default. A quote to a German customer with dates in US format and a decimal point where they expect a comma reads as careless.
Why store the file rather than regenerate it?
Because the document is a record of what was sent. A quote references prices, terms, and a company address as of the day it went out. Regenerate it in six months and the prices have changed, the terms have a new clause, and the customer has a PDF that doesn’t match the one your system now produces from the same quote number. That argument has been had, and it’s not one you want to have with a lawyer present.
So: generate once, store the file in object storage with a version number and a timestamp, record which version was sent to whom, and serve that file from then on. If the quote is revised, that’s version two, a new file, and the old one stays. Invoices are stricter still; in most jurisdictions the invoice you issued is the invoice, and regenerating it with different data is a problem for the accountant. This is one of the reasons quoting belongs in a system with records rather than a spreadsheet, which our post on quoting tools goes into.
Storage costs almost nothing. A PDF quote is a few hundred kilobytes, and ten thousand of them is a few gigabytes.
What about e-signatures and delivery?
If the document needs signing, the e-signature providers (DocuSign, Dropbox Sign, PandaDoc, and others) all accept a generated PDF and handle the signing, the audit trail, and the signed copy. Generate the PDF, send it to the provider, store the signed version they return. Building signature capture yourself is possible and rarely worth it, because the legal weight of the signature comes from the provider’s audit trail.
Delivery is email in most cases, and the thing to get right is that the email links to a stable copy of the document rather than attaching a file that then differs from what’s in the system. A link to a portal page that shows the document and its status (sent, viewed, accepted) also tells you whether the customer opened it, which sales teams like. The B2B ordering portal is where this tends to live for businesses with account customers.
How much work is it?
For a straightforward document type, quote or invoice, with the headless-browser approach: a few days including the template, the page-break handling, the storage, and the versioning, in an application that already has the data. A report with charts and multiple tables is more, mostly in the template. Costs sit at the small end of our custom web application cost ranges when added to an existing system; if the system doesn’t exist yet, the document generation is a feature of the larger build.
The part that gets underestimated is the testing with real data: the customer with the 40-word company name, the quote with 200 line items, the address with five lines. Budget the time for it.
Document generation, with storage, versioning, and delivery done properly, is part of most of the custom software we build, and it’s a common first request from businesses whose current documents come out of Word with the numbers typed in by hand.
Frequently asked questions
Can we let customers download a PDF of any page of the app?
You can, and the result will look like a printed web page. For anything a customer will keep or forward, build a proper template. The print dialog is fine for internal use.
Should the PDF be generated when the record is created or when it’s requested?
When it’s sent or issued, then stored. Generating on request means the document can change between views. Generating on creation of a draft wastes effort on documents that get revised before they go out.
How do we handle a change to the letterhead or terms?
New templates apply to new documents. Existing stored documents keep the letterhead and terms they were issued with. If a customer needs a reissued document, that’s a new version with a new date, and the old one is kept.
Is HTML-to-PDF good enough for a designed document?
Yes, with print CSS and some care. Anything a web page can show, the PDF can show, including the brand’s fonts and colours. The limits are the print-specific features (page numbers, running headers), which take setup but work.
What about accessibility and archiving formats?
Tagged PDFs for accessibility and PDF/A for long-term archiving are both achievable with the headless-browser and library approaches, with extra configuration. If you have a regulatory need for either, say so at the start, because it affects the tooling choice.
Can the same template produce Word documents too?
Not from the HTML approach without a separate conversion step. If customers need editable Word versions, the document-template approach (a Word template filled from data) is the one that produces both formats from one source.