Register your interest: Tag @Cody, get an agent
BlogEngineering

How to automate PDF generation: the rendering decision

Choosing how to render decides everything afterwards — pagination, fonts, tables that break across pages, and whether the output is accessible. A comparison, and the details that bite.

Rithul PalazhiRithul Palazhi11 min read

Summarize with AI

How to automate PDF generation: the rendering decision
On this page

Generating a PDF from data is one of those tasks that looks solved until the first real document. Then a table breaks across a page boundary in the middle of a row, a heading lands alone at the bottom of a page, a font renders differently on the server than it did locally, and the accessibility requirement nobody mentioned turns out to apply.

Almost all of that is decided by how you render. Picking that deliberately, rather than reaching for the first library, determines how much of the rest you will be fighting.

What we'll cover

Four ways to render, honestly compared

HTML and CSS through a headless browser. Familiar tooling, good typography, and the widest talent pool. Heavy per document, and print CSS support varies in the areas you need most.

A dedicated print engine built for paged media. Much better pagination control, a licence cost, and a smaller pool of people who know it.

A programmatic PDF library, drawing elements directly. Precise, fast, and you are implementing layout yourself, which is more work than it sounds for anything with flowing text.

A template service. Fastest to a working document, least control, and a dependency for something that may be legally significant.

Choose by the document. An invoice with a fixed structure suits a programmatic library; a report with flowing content and tables suits HTML or a print engine.

Changing later is expensive, because the templates do not port, which is the argument for spending an afternoon on this decision.

Pagination is the hard part

Content flows and pages do not. Every difficult PDF problem is a version of this.

Orphans and widows — a line or a heading stranded alone — which print CSS can control and browsers implement inconsistently.

Keeping things together: a heading with its first paragraph, an image with its caption, a total with its table.

Repeating table headers on each page, which is essential for anything longer than a page and is one of the weaker areas of browser-based rendering.

Page numbering, including "page 2 of 7", which requires knowing the total and is therefore not a single-pass operation.

Fixed headers and footers with page-specific content, which is straightforward in a print engine and awkward elsewhere.

Fonts, which break in production

The server does not have your fonts. This is the most common cause of a document that looked right in development and is wrong in production.

Embed rather than reference, always, so the document renders identically everywhere it is opened.

Check the licence. Embedding a font in a distributed document is a use that some licences treat differently from web use.

Fallbacks change the layout, not just the appearance, since a substituted font has different metrics and reflows everything.

Non-Latin scripts need real coverage, and a font that looks complete may be missing the range you need for one market.

Test with real data, including the longest name and the largest number you will actually encounter, because layout breaks at the extremes rather than the average.

Building it with CodeWords

CodeWords connects to more than 3,000 integrations, so the data source, the rendering, the storage and the delivery can sit in one automation.

  1. Open CodeWords and start a new automation.
  2. Describe what should happen in plain language to Cody, the automation builder: the data source, the document, and where the finished file goes.
  3. Authorize the connections — the database, spreadsheet or API holding the data, and the storage or delivery destination.
  4. Describe the exceptions: missing data, a value too long for its space, a document that exceeds an expected page count.
  5. Generate against real historical records, including the awkward ones, before anything is sent.

You describe the outcome; Cody builds it, connects it, and deploys it. The free plan covers light use, with Pro at $39 per month and Business at $100 per month as usage grows; details are on the pricing page.

Tables that survive a page break

The single most common source of an ugly generated document.

Repeat the header row on every page, without exception.

Do not split a row across pages, which needs explicit instruction in most renderers.

Keep a total with its table, or a reader sees a number with no context at the top of a page.

Handle the overflowing column. A long value either wraps, truncates with an indication, or shrinks — decide which rather than letting the renderer choose.

Say "continued" on a table spanning pages, which is a small courtesy that makes a long document readable.

Test the boundaries deliberately: a table of exactly one page, one row more, and one row less. Those three cases find most pagination bugs.

Accessibility, which may be required

Tagged PDF is the accessible form, carrying structure rather than only visual appearance, and most generation approaches do not produce it by default.

Public sector and many regulated contexts require it, and the requirement is frequently discovered after the pipeline is built.

Reading order must be correct, which is not automatically the same as the visual order in a multi-column layout.

Tables need real structure with identified header cells, or they are unreadable to a screen reader.

Images need alternative text, which has to come from your data rather than being generated.

Check the requirement first. Retrofitting tagging onto a chosen renderer that does not support it is a rebuild, and it is a common and avoidable expensive surprise.

Documents that are records

An invoice is a record, and regenerating it later from live data can produce a different document, which is a real problem.

Store the generated file, not only the data, for anything with legal or financial weight.

Record the version of the template used, since template changes alter historical documents if you regenerate.

Number sequentially where required, with gaps handled deliberately, because in several jurisdictions invoice numbering is regulated.

Digital signatures are a separate capability from generation and need planning if the document requires one.

Retention applies to the documents as it does to the underlying records, including deletion obligations.

Delivering the document, which is its own problem

Generation is half of it. How the document reaches the person is where a surprising share of the support load comes from.

Email attachments have size limits that vary by recipient, and a large report will bounce silently at some of them.

A link is usually better, and it needs to be authenticated, because a guessable link to an invoice is a data breach.

Expiring links protect you and frustrate people, so set the expiry generously and make regeneration easy rather than short and strict.

Name the file meaningfully. An invoice called document.pdf is a support ticket; one named with the invoice number and date is not.

Say what it is in the covering message, since an unexplained attachment gets deleted by anyone with reasonable security habits.

Keep a record of delivery, because "I never received it" is the most common dispute about a generated document and it is easy to answer if you kept the log.

Making it survive

Generate to storage first, then deliver, so a delivery failure does not lose the document.

Make it idempotent on a document id, since the same invoice generated twice with different numbers is a problem.

Set resource limits. Headless browser rendering can consume a lot of memory, and one pathological document should not take down the service.

Validate the output: page count within an expected range, file size plausible, and the document actually opens.

Report the outcome: documents generated, failed, and anything outside the expected size or page count.

Limits worth knowing about

Headless browser rendering is resource-heavy, which matters for bulk generation and is fine for one document at a time.

Print CSS support varies between engines, particularly for page breaks, running headers and generated content.

Precise page counts are not known in advance, which affects anything referencing a total page count.

PDF/A for archival is a different output with its own constraints, including font embedding rules.

Large bulk runs need batching and concurrency control, or they will exhaust memory in a way that presents as random failure.

What to build first

One document, rendered from real historical data, including the worst examples you can find — the longest names, the largest tables, the record with missing fields. It takes an afternoon and it tells you whether your chosen rendering approach can do what you need, which is the decision everything else depends on.

Two habits make the difference. Embed every font from the start, since the font that exists on your machine and not on the server is the classic production failure. And check whether accessibility is required before choosing a renderer, because retrofitting tagged output onto an approach that cannot produce it means starting the templates again.

Frequently asked questions

What is the best way to generate PDFs from data?

It depends on the document. Fixed-structure documents like invoices suit a programmatic library; reports with flowing content and tables suit HTML with a headless browser or a dedicated print engine. Changing later is expensive because templates do not port.

Why does my PDF look different in production?

Almost always fonts. The server does not have the fonts your machine does, and the substitute has different metrics, so everything reflows. Embed fonts rather than referencing them.

How do I stop tables breaking badly across pages?

Repeat the header row, prevent rows splitting, keep totals with their tables, decide explicitly how overflowing columns behave, and test a table of exactly one page plus one row more and one less.

Does my generated PDF need to be accessible?

Check before choosing a renderer. Public sector and many regulated contexts require tagged PDF, most generation approaches do not produce it by default, and retrofitting it means rebuilding the templates.

Should I store generated documents or regenerate them?

Store them, for anything with legal or financial weight. Regenerating from live data later can produce a different document, and template changes alter historical output.

How do I do "page 2 of 7"?

It requires knowing the total page count, which is not available in a single pass. Print engines handle it natively; browser-based rendering usually needs a second pass or an engine-specific feature.

Is headless browser rendering fast enough for bulk generation?

Per document it is heavy. For bulk runs, batch with concurrency control and resource limits, or a pathological document will exhaust memory in a way that looks like random failure.

What should I test with?

Real historical data, including the extremes — the longest name, the largest table, the record with missing fields. Layout breaks at the extremes, not the average, and synthetic test data rarely contains them.

Usually a link, authenticated, since attachment size limits vary by recipient and a large report bounces silently. A guessable link to an invoice is a data breach, so authenticate it rather than relying on obscurity.

What is the most common support issue with generated documents?

"I never received it." Keep a delivery record, name the file meaningfully rather than document.pdf, and explain what it is in the covering message so it does not get deleted unopened.

Get started today

Your first workflow is free to build.

Describe what you need. Cody handles the build, the connections, and the deployment.