Paginate a large HTML table across pages
At a glance
Section titled “At a glance”Pass one large <table> to writeHtml(). The engine paginates it across as many PDF pages as the rows need. The <thead> repeats at the top of every page, so each page works as a complete table. This recipe renders a 91-row report that flows across several pages. You do not split the table yourself or compute page breaks.
Install
Section titled “Install”composer require nextpdf/coreConceptual overview
Section titled “Conceptual overview”When a table is taller than one page, keep it as a single <table> element. The engine measures each row, fills the usable page height, opens a new page, and continues the same table. It renders the <thead> rows again at the top of each continuation page. Continuation pages keep the document’s top and bottom margins, so the first row on a new page starts below the top margin, not at the page edge.
Place header cells in a <thead> and data rows in a <tbody>. Only the <thead> repeats. Keep each row splittable. A single row taller than the usable page height cannot be paginated and raises UnsplittableContentException.
API surface
Section titled “API surface”| Symbol | Location | Role |
|---|---|---|
Document::writeHtml(string $html): static | NextPDF\Core\Concerns\HasTextOutput | Render HTML and paginate tables as needed. |
Document::createStandalone(): self | NextPDF\Core\Document | Create a standalone document. |
Document::addPage(): static | NextPDF\Core\Document | Open the first page and set the page size and margins. |
Code sample — Quick start
Section titled “Code sample — Quick start”<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$rows = '';for ($i = 1; $i <= 91; $i++) { $rows .= "<tr><td>{$i}</td><td>Item {$i}</td><td>In stock</td></tr>";}
$doc = Document::createStandalone();$doc->addPage();$doc->writeHtml( '<table>' . '<thead><tr><th>#</th><th>Name</th><th>Status</th></tr></thead>' . "<tbody>{$rows}</tbody>" . '</table>');$doc->save(__DIR__ . '/large-table.pdf');The 91 rows flow across several pages, and the #/Name/Status header repeats on each page.
Code sample — Production
Section titled “Code sample — Production”This self-contained example styles the header, stripes the rows, and writes the PDF to the path the harness supplies.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$rows = '';for ($i = 1; $i <= 91; $i++) { $bg = $i % 2 === 0 ? '#F8FAFC' : '#FFFFFF'; $rows .= "<tr style=\"background-color: {$bg};\">" . "<td style=\"border: 1px solid #CBD5E1; padding: 4px;\">{$i}</td>" . "<td style=\"border: 1px solid #CBD5E1; padding: 4px;\">Item {$i}</td>" . "<td style=\"border: 1px solid #CBD5E1; padding: 4px;\">In stock</td>" . '</tr>';}
$html = <<<HTML<table style="width: 100%; border-collapse: collapse;"> <thead> <tr style="background-color: #1E3A8A; color: #FFFFFF;"> <th style="border: 1px solid #1E3A8A; padding: 6px;">#</th> <th style="border: 1px solid #1E3A8A; padding: 6px;">Name</th> <th style="border: 1px solid #1E3A8A; padding: 6px;">Status</th> </tr> </thead> <tbody>{$rows}</tbody></table>HTML;
$doc = Document::createStandalone();$doc->setTitle('Inventory report');$doc->addPage();$doc->writeHtml($html);
$out = getenv('NEXTPDF_OUT');$doc->save($out !== false ? $out : __DIR__ . '/paginate-large-html-tables.pdf');
echo "Wrote the paginated table PDF\n";Continuation-page margins
Section titled “Continuation-page margins”Continuation pages use the document’s top and bottom margins. The first row after a page break starts below the top margin, and the last row before a break stops above the bottom margin. This keeps a long table from clipping against the page edge. The same rule applies to a multi-page block and a multi-page table.
Edge cases & gotchas
Section titled “Edge cases & gotchas”- Pagination warnings are quiet on success. When a table flows cleanly across pages, it emits no warning. The engine raises a
TABLE_ROW_OVERFLOWwarning only for a degraded layout: when there is no page-break sink, or when a row must be relocated to lead a page. Treat the warning as a signal to simplify the table, not as a failure. - Put the header in
<thead>. Only<thead>rows repeat. A header row left in<tbody>appears once. - Rows must be splittable. A single row taller than the usable page height raises
UnsplittableContentException. Break up the content of that row, or shorten it. rowspandegrades across a break. Arowspancell that crosses a page boundary fragments (ADR-007). For groupings that must remain intact across pagination, use a category-header row instead. See Unsupported CSS features.
Performance
Section titled “Performance”Rendering scales linearly with the row count. The engine streams output page by page and never retains a document tree, so memory for a long table does not grow with the page count. The budget for this recipe is wall_ms: 2000, peak_mb: 96.
Security notes
Section titled “Security notes”Validate the row count and cell length of user-supplied data to keep output size bounded. The engine renders text instead of interpreting it, and it runs no script.
Conformance
Section titled “Conformance”| Statement | Spec | Clause | reference_id |
|---|---|---|---|
| A table header group repeats across the fragmentation containers into which a table is split. | W3C CSS Tables 3 | css_tables_3#x1.x7.x2 | 53622ccb1bce2a0cc53bd70919fa4633a9376e2050f63a31a3fde9cb6595ec78 |
Commercial context
Section titled “Commercial context”Not applicable.
See also
Section titled “See also”- HTML: HTML+CSS to PDF rendering subsystem — single-pass rendering and pagination behavior.
- Lay out tables in HTML — column sizing,
colspan, and borders. - Paginate long HTML content — page breaks for mixed block content.
- CSS support matrix — support status for tables and pagination.