← dompdf.js Studio

The addPage API Complete Guide: From Single Pages to Multi-Page Documents

addPage is the most central and most frequently used API in dompdf.js. It renders a piece of HTML or a real DOM element as a PDF page. Most tutorials only demonstrate the simplest single-page usage, but real business code is dominated by multi-page documents: covers, tables of contents, body chapters, and appendices, where every page may differ in layout and size. addPage was designed exactly for this — it can be called multiple times, accepts per-page options, and, combined with automatic pagination, assembles a complete multi-page document from a single call chain. This guide starts with the addPage signature, compares the two input forms (HTML strings versus DOM elements) and when to prefer each, explains how per-page options override constructor defaults, and then walks through automatic pagination, @page rules, headers, footers, and page numbers. Along the way we cover the timing traps that produce missing images, the pagination controls that keep tables intact, and the performance habits that keep multi-page exports fast. By the end you will have a complete practical path from single-page exports to complex multi-page documents, and you will stop treating addPage as a black box. We also cover a per-page validation habit that catches bad records before they reach the renderer, and a wrapper-function pattern that gives every export in the project one consistent entry point. The guiding principle throughout is simple: know what your content is, know where it lives, and addPage will do the rest — reliably, page after page.

The addPage Signature and Its Two Input Forms

addPage(htmlOrElement, options) takes two arguments: the content — either an HTML string or a live DOM element — and an optional per-page configuration object. The call itself is synchronous; the genuinely time-consuming rendering and writing happen later in the save stage. Understanding this timing matters more than it looks: many developers assume addPage performs the render, when in fact it only collects content. That misconception leads to debugging the wrong layer when output comes back wrong.

HTML strings are the right tool for templated, dynamic content. Data concatenation, loop-generated rows, and template fragments delivered from the server can all be passed directly as strings without ever mounting them to the page. Strings also compose cleanly with frontend template engines, keep the code cohesive, and are trivially unit-testable — the template can be maintained and verified independently, and one change propagates everywhere it is used.

DOM elements suit content that already exists on the page: report containers, form regions, and live charts. Passing the element skips serialization entirely, and the element's current styles, images, and layout state are captured as-is. The catch is timing: the element must have finished rendering and loaded all its resources. Exporting before async data arrives produces output with missing content, a failure mode that is easy to miss in quick visual checks and annoying to trace back to a race condition.

Choose the input form by where the content lives, not by habit. Content generated on the fly belongs in strings; content already rendered belongs as elements. Mixing both in one document is fully supported and often the cleanest design — a string-rendered cover, element-captured charts, and a string-rendered appendix can coexist in a single document without friction.

A useful hybrid trick: when a template mixes fixed styling with dynamic data, write the static shell as a constant string, then insert the dynamic values through a template literal and pass the finished string to addPage. The static shell stays maintainable in one place, data changes only require a rebuild of the string, and the logic stays simple and easy to reason about — a pattern that scales well as templates grow.

Per-Page Options: Overriding Constructor Defaults

The second argument to addPage supports per-page settings such as format and margin. Fields you omit continue to use the constructor's global defaults. This mechanism lets a single document mix multiple page specifications: a cover, body pages, and an appendix can each carry independent settings without touching one another. You get the flexibility of per-page control without repeating global configuration on every call.

The typical usage pattern is a loop that passes different options per page, or distinct option combinations at different stages of the document. Keep in mind that every addPage call appends a new page and page order equals call order. When assembling a document, the sequence is part of the contract: cover before body, body before appendix. Getting the order wrong silently produces a document with shuffled structure, so treat the call order as intentional design rather than an incidental detail.

When the whole document uses one uniform specification, the simplest approach is to configure the constructor once and pass only content to addPage. The code is shortest and least error-prone, and the two-layer responsibility split stays clean: settings that are stable across pages live in the constructor, settings that vary per page live in addPage. Together the two layers cover the vast majority of real requirements, and the intent of the code remains legible at a glance.

A useful mental model is that addPage options are a diff against the constructor defaults. Whatever you pass wins for that page; whatever you omit inherits. Designing templates around this model — constructor for the common case, addPage for exceptions — keeps configuration minimal and makes the document structure obvious to anyone reading the code, including your future self.

When page options must be computed at runtime, build the configuration object first, then pass it to addPage, rather than inlining a dense ternary inside the call. A conditional branch that decides format based on content length, followed by a single addPage invocation, reads far more clearly, is easier to log and test, and keeps the per-page contract explicit.

A final robustness habit for multi-page documents: validate each page's content before it reaches the renderer. Check that the HTML string is non-empty, that data placeholders were actually replaced (no stray ${...} tokens left in the markup), and that the per-page options object is well-formed. A tiny validation function called at the top of each page render function catches bad records early — before a single malformed page drags the whole document into a failed render — and it turns 'the export broke somewhere' into 'page seven's data is invalid', which is a vastly better place to start debugging. The check costs a few lines and pays for itself the first time dirty data arrives from an upstream system.

Complete Multi-Page Example

This example assembles a complete report from four parts — cover, table of contents, body chapters, and appendix. Note how per-page overrides are used sparingly: the cover sets its own margin, the body loop and appendix rely on constructor defaults, and the page order is exactly the call order. This is the pattern to internalize for any multi-page document.

import { DomPDF } from 'dompdf.js';

const pdf = new DomPDF({ format: 'A4', margin: '20mm' });

// Page 1: cover (own margin, overriding the constructor default)
pdf.addPage(coverHTML, { margin: '15mm' });

// Page 2: table of contents (constructor defaults)
pdf.addPage(tocHTML);

// Pages 3-N: body chapters, appended in a loop
const chapters = [/* chapter data */];
for (const chapter of chapters) {
  pdf.addPage(renderChapter(chapter));
}

// Final page: appendix
pdf.addPage(appendixHTML, { format: 'A4' });

// Page order = addPage call order: cover, TOC, chapters, appendix
pdf.save('full-report.pdf');

Automatic Pagination and @page Rules

When the content of a single addPage call exceeds the page capacity, dompdf.js paginates automatically: overflowing content flows onto subsequent pages in content order, and block-level elements such as tables and images are handled sensibly at page boundaries. You do not need to split long content by hand — a document dozens of pages long can be produced from a single addPage call, which dramatically simplifies long-document generation logic.

The pagination boundary is determined jointly by the content width and the remaining height on the current page, and the content width itself is influenced by page size and margins. To keep pagination predictable, declare global page styles such as background and margins in an @page rule inside the HTML, and pair it with a container width that matches the target page size. When the web layout and the PDF output share the same width model, pagination drift between browser and PDF simply stops happening.

When you need precise control over where breaks occur, use pagination-related styles on elements in the template: blocks that should stay together on one page (table rows, an image with its caption) can be declared unbreakable, and chapters that must start on a fresh page can force a page break. These controls are invaluable in long documents: they prevent orphaned lines and tables sliced in half at a page boundary — the kind of amateurish pagination results that immediately undermine a document's credibility.

A good workflow is to design the template for the page, not the other way around. Lay out content for the known content-area width, mark the elements that must never split, then verify the breaks visually in the exported PDF. Iterating on the template is far cheaper than post-processing the PDF, and the template remains the single source of truth for pagination behavior.

One pagination technique worth adopting: split long tables that must span pages into several shorter table blocks separated by break-friendly spacing, so every page carries complete rows and no row is ever sliced. You trade a little layout continuity for a far more professional reading experience — especially important for financial statements and multi-page itemized lists, where cut-off rows are immediately noticeable.

Headers, Footers, and Page Numbers

Almost every multi-page document needs headers and footers: the document title and company logo in the header, page numbers and dates in the footer. dompdf.js supports header and footer rendering, and page numbers support dynamic placeholders such as current page and total pages, which are replaced with real values at output time. You never compute page numbers by hand, and they stay correct automatically when the page count changes.

Declare headers and footers as a global configuration at the constructor or template level rather than repeating them in every addPage call. Cover pages usually do not want a header or footer at all; the mechanism for excluding selected pages handles that cleanly, keeping the cover pristine. Details like this are where documents visibly gain professionalism, and they are exactly the points reviewers notice first.

Header and footer heights must be coordinated with margins: a header occupies part of the margin band, and margins that are too small cause the header to overlap or visually crush the body text — or get clipped in print. When designing the template, decide header and footer heights first, then derive the top and bottom margins from them. Adjusting the two values together yields the most stable, best-looking result, and printing never surprises you with a clipped header.

A final tip: keep header and footer content short and legible at the chosen font size. A one-line header with the document title and a footer with page X of Y covers the overwhelming majority of business documents. Resist the temptation to stuff the footer with metadata such as full company names, addresses, and phone numbers — the more information you add, the more it collides with the body visually and the more likely print layout breaks. Restraint reads as polish.

Common Pitfalls and Performance Advice

Q: Images are missing from the output because export fired before they loaded? A: Wait for images to finish loading before calling addPage. For DOM elements, await each image's decode or use Promise.all over all resources; for HTML strings, prefer inline base64 images so the timing problem disappears at the source and rendering is stable by construction.

Q: A single addPage with very long content renders slowly? A: Split long documents into multiple addPage calls by chapter. Each call carries less content, pagination computation pressure drops, and progress feedback becomes finer-grained — the user experience improves visibly, and when something breaks, the search space shrinks to a single chapter instead of the whole document.

Two further performance habits pay off in every project: avoid deeply nested markup and excessive background gradients in templates, since both add snapshot-collection cost; and reuse the same URL for an image referenced on multiple pages so the resource is encoded exactly once. In multi-page documents, file size drops noticeably and export speed follows, and in batch scenarios the savings compound dramatically across hundreds of documents.

Finally, consider wrapping addPage behind a single entry function in your project that handles template assembly, resource waiting, and option merging, so business code calls one function instead of the raw API. Every export then runs through the same logic, future optimizations such as progress reporting land in exactly one place, and team members align on a shared pattern instead of inventing variations.

⚡ 现场演示(点击生成 PDF)

下面的按钮用 dompdf.js 在浏览器端实时生成 PDF,无需后端:

Hello from dompdf.js!

这是由 dompdf.js 渲染的示例 PDF 内容。