Recommended Free Tools
Use a real HTML table with a <thead> and <tbody>, then set thead { display: table-header-group; } in print CSS. Puppeteer’s page.pdf() renders with the print media type, so Chromium can place that header group at the top of every PDF page occupied by the table.
The complete pattern is semantic table markup, print-only CSS, and a PDF call such as await page.pdf({ path: 'report.pdf', format: 'A4', printBackground: true }). The sections below show a runnable fixture, production options, and fixes for layouts where the heading still disappears.
Quick start: a table header that repeats
Install Puppeteer in a new project:
npm install puppeteer
Save this as repeat-headings.js. It creates enough rows to cross several A4 pages, applies the print rule, and writes report.pdf:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
const rows = Array.from({ length: 90 }, (_, i) => `
<tr>
<td>${i + 1}</td>
<td>Item ${i + 1}</td>
<td>${i % 3 === 0 ? 'Complete' : 'In progress'}</td>
<td>${(i + 1) * 12}</td>
</tr>`
).join('');
await page.setContent(`<!doctype html>
<html>
<head>
<meta charset='utf-8'>
<style>
@page { size: A4; margin: 18mm 14mm; }
body { font-family: Arial, sans-serif; font-size: 10pt; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #999; padding: 6px; text-align: left; }
thead { display: table-header-group; }
tfoot { display: table-footer-group; }
tr { break-inside: avoid; }
</style>
</head>
<body>
<h1>Quarterly report</h1>
<table class='report'>
<thead>
<tr>
<th>No.</th><th>Item</th><th>Status</th><th>Total</th>
</tr>
</thead>
<tbody>${rows}</tbody>
</table>
</body>
</html>`, { waitUntil: 'networkidle0' });
await page.pdf({
path: 'report.pdf',
format: 'A4',
printBackground: true
});
} finally {
await browser.close();
}
})();
Open report.pdf and inspect the second and later pages. The same column row should appear above each continuation of the table. A one-page fixture cannot demonstrate repetition, so always test with data that really crosses a page boundary.
#1 Best Overall
- INNOVATIVE CARTRIDGE-FREE PRINTING — No more dealing with lots of tiny ink cartridges; With this wireless document and photo printer each ink bottle set is equivalent to about 90 individual cartridges²
- LESS FREQUENT INK REPLACEMENT — Replacement ink bottles don't have to be changed nearly as often as ink cartridges¹; When you choose this combination printer, scanner and copier you can print up to 4,500 pages black/7,500 color³
- COLOR PRINTING — Up to 2 years of ink in the box4 (and with every replacement ink set) for fewer out-of-ink frustrations
- ZERO CARTRIDGE WASTE — By using an Epson EcoTank printer you can help reduce the amount of cartridge waste ending up in landfills
- HOME PRINTER DESIGNED FOR RELIABILITY — The Epson EcoTank ET-2800 All-in-One Supertank Color Printer creates vivid, detailed prints and documents thanks to Micro Piezo Heat-Free Technology; Fire off 10 ISO pages per minute1 to easily finish large jobs
Use semantic table structure
Put column labels in thead
Keep the heading row inside <thead> and the records inside <tbody>. This gives Chromium a table-header group rather than an ordinary row that happens to be styled like a header:
<table class='report'>
<thead>
<tr><th>Item</th><th>Status</th><th>Total</th></tr>
</thead>
<tbody>
<tr><td>A-100</td><td>Complete</td><td>42</td></tr>
</tbody>
</table>
Multiple header rows can be placed in the same thead when a report needs grouped columns. Do not put those rows in a separate block above the table or replace the table with a collection of div elements.
Apply the print display value
Use the rule globally or scope it to the report table:
@media print {
table.report thead {
display: table-header-group;
}
}
table-header-group is the display role that tells a print user agent the group may be repeated on pages spanned by the table. The HTML default styling already maps thead to that role, but declaring it in print CSS makes the intent explicit and protects the rule from an application reset.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteGenerate the PDF from an existing page
For a URL that already renders the report, navigate first and then call page.pdf():
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com/report', { waitUntil: 'networkidle0' });
await page.pdf({
path: 'report.pdf',
format: 'A4',
printBackground: true,
margin: { top: '18mm', right: '14mm', bottom: '18mm', left: '14mm' }
});
} finally {
await browser.close();
}
})();
Replace the URL with your report endpoint. If the page loads data after navigation, wait for a report-specific selector (for example, await page.waitForSelector('table.report tbody tr')) before creating the PDF. If it uses a client-side loading state, wait for that state to disappear as well; otherwise you may capture an empty or incomplete table.
Rank #2
- CARTRIDGE-FREE PRINTING — Print lab-quality photos, graphics and creative projects; Get vibrant colors and sharp text with Epson's high-accuracy printhead and Claria ET Premium 6-color inks
- INK BOTTLES — Save on photos1 and creative projects with affordable in-house printing; All-in-one printer allows you to print 4" x 6" photos for about 4 cents each vs. 40 cents with traditional ink cartridges1
- LESS FREQUENT INK REPLACEMENT — Replacement ink bottles don't have to be changed nearly as often as ink cartridges¹; Printer, scanner and copier lets you print up to 6,200 color pages³
- PRINT FOR LONGER — Up to 2 years of ink in the box² (and with every replacement ink set) for fewer out-of-ink frustrations with this wireless printer
- ZERO CARTRIDGE WASTE — Epson EcoTank printer helps reduce the amount of cartridge waste ending up in landfills; Cartridge-free printer uses high-yield ink bottles; Each replacement ink bottle set is equivalent to about 100 individual ink cartridges⁴
Pagination options that change the result
page.pdf() uses print media by default. These options determine the printable area and therefore where a table breaks:
| Option | Purpose | Important interaction |
|---|---|---|
format |
Sets a named paper size such as A4. | Available page width and height affect the number of rows per page. |
width and height |
Set explicit PDF dimensions. | Use these instead of a named format when the output has a custom size. |
margin |
Reserves top, right, bottom, and left space. | Larger margins leave less room for rows and can create more page breaks. |
preferCSSPageSize |
Lets a CSS @page size take priority. |
Review it together with format, width, and height; conflicting choices can change pagination. |
printBackground |
Includes CSS backgrounds in the PDF. | It affects appearance, not whether a thead repeats. |
displayHeaderFooter |
Enables Puppeteer’s document-level header and footer templates. | These templates are separate from a table’s repeating column headings. |
For a CSS-sized report, use an @page rule and opt in explicitly:
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →@page { size: 210mm 297mm; margin: 16mm; }
await page.pdf({
path: 'report.pdf',
preferCSSPageSize: true,
printBackground: true
});
Use headerTemplate and footerTemplate for document labels, dates, URLs, or page numbers. Use thead for labels that describe table columns. Mixing the two roles makes maintenance and visual testing harder.
Keep the heading visible and the rows printable
Do not override the table display
Search your application’s print stylesheet for rules that set thead, the table, or an ancestor to display: block, display: none, display: flex, or another incompatible value. Remove the override or place the report rule later and scope it narrowly:
@media print {
table.report { display: table; }
table.report thead { display: table-header-group; }
table.report tbody { display: table-row-group; }
}
Keep the table as a table
Grid components often render visually similar div elements. They can be useful on screen, but Chromium cannot repeat a real table header group when there is no table header group. Render a print-specific table, or switch the component to semantic table markup for the print view.
Prevent awkward row splits carefully
A long row can still make a page break look confusing. The fixture uses tr { break-inside: avoid; } to request that rows stay together. If a row is taller than the printable area, Chromium must still split or move content; the rule cannot make an impossible layout fit.
Rank #3
- SET IT UP ONCE AND PRINT WITH CONFIDENCE. No complicated maintenance. Just easy, reliable printing you can count on.
- INK FOR YEARS. NOT MONTHS. Up to 2 years of ink included. Get thousands of pages of cartridge-free printing. More pages, less hassle
- KEEPS PRINTING WELL AFTER COMPETITORS HAVE QUIT. No complex maintenance. Sharper text, richer colors.[2] Only with HP Smart Tank
- PREMIUM SUPPORT - Strong technical expertise to solve issues faster
- THE LAST PRINTER YOU'LL EVER NEED. Enjoy years of refillable, cartridge-free printing.
Troubleshooting repeated headings
The header appears only on page one
- Confirm the labels are inside
thead, not merely the first row oftbody. - Confirm the print rule is loaded before
page.pdf()runs and is not disabled by a later selector. - Inspect the generated PDF, not only the browser’s screen view; the rule is under
@media print.
The PDF has only one page
There is nothing to repeat until the table crosses a page boundary. Add realistic rows to a test fixture, or reduce the paper size and margins temporarily to force a break. Restore production settings after verifying the continuation pages.
The header or rows are missing
Wait for the data to finish loading before calling page.pdf(). Use a report-specific selector, check that the selector matches at least one row, and make sure an error state has not replaced the table.
Pagination changed after a CSS update
Check @page, margins, font loading, row padding, and any change to format, explicit dimensions, or preferCSSPageSize. A small change to printable width or line wrapping can move a row to the next page while the header rule remains correct.
A complex layout still fails
Nested tables, transformed elements, forced page breaks, unusual display overrides, and version-specific print bugs can affect output. Puppeteer issue #10020 reports a case in which display: table-header-group was ignored in PDF output. Treat that as a compatibility warning, not evidence that every current release fails: test the exact Puppeteer and Chromium versions deployed by your application.
Build a visual regression test
- Generate a deterministic fixture with a fixed number of rows, fixed fonts, and the same CSS used in production.
- Make the fixture span at least three pages so both the first continuation and a later continuation are checked.
- Assert that the PDF was created and has the expected page count using your normal PDF test tooling.
- Render pages to images in CI or inspect them manually and verify that every continuation starts with the column labels.
- Run the test again whenever Puppeteer, its bundled Chromium, the print stylesheet, or page-size settings change.
This catches regressions that a short one-page sample will never reveal.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Performance, reliability, and operating cost
Local Puppeteer gives you direct control over Chromium, CSS, fonts, network access, and the output path. The trade-off is operational: your process must launch and maintain a compatible browser, wait for all report data, and store or deliver the resulting files. Large tables, web fonts, images, and JavaScript-heavy pages increase rendering work, so wait on the specific readiness condition you need rather than using an unnecessarily long fixed delay.
Rank #4
- Wireless Bluetooth Printer: Portable thermal printer compatible with iPhone, Android phones, iPad and tablet computers via Bluetooth. For smartphones, please download the "Nada Print" App. You can also connect to laptops and computers for printing using a USB-C cable. (Note: Laptops and computers can only be connected via USB and require the installation of a driver first. Bluetooth connection is not supported.)
- No-ink printing: Only supports US Letter and A4 size thermal paper.(Doesn't support regular paper) The no-ink portable thermal printer uses direct thermal technology, requiring no ink, toner or ribbons, making it environmentally friendly, cost-effective and time-saving. The thermal printer package comes with a roll of US Letter thermal printing paper. Note: When installing the paper, remember to switch the paper size switch on APP
- Clear Print: NDYIN N80 portable thermal printer adopts high-definition printing technology, with a 203DPI resolution to provide you with clear printing results. This mobile printer is compatible with roll paper, folded paper and tattoo transfer paper, supporting printing from your mobile phone PDF, Word, pictures and web pages anytime and anywhere. It is recommended to use our NDYIN thermal paper to achieve good printing quality
- Portable wireless printer for travel: The thermal printer is equipped with a built-in 1500mAh rechargeable battery, which can print 160 sheets of 8.5" x 11" thermal paper after being fully charged. It weighs only 1.5 pounds and is compact in size. This ink-free portable printer can be easily carried in a backpack or briefcase! It is perfect for business travel, cars, small offices, construction sites, schools and homes. You can print documents, contracts, invoices and boarding passes anytime and anywhere
- The N80 thermal printer has a wide range of uses. The package includes the N80 printer, a roll of US Letter paper(7m/roll), a user manual, a guide card, a type-C soft cable and a type C adapter. Note: The charging adapter is not included. Special thermal paper is required for use; ordinary paper cannot be used. This ink-free portable thermal printer is suitable for various scenarios such as home, school, travel, office, and outdoor, meeting the printing needs of different groups of people. This tattoo template printer is also compatible with tattoo transfer paper, making it an ideal choice for tattoo art
For reproducible pagination, pin the Puppeteer version used in deployment, keep paper size and margins explicit, and use the same browser channel in development and production. Record the options passed to page.pdf() alongside the report version so a later CSS change can be correlated with a pagination change.
Or skip the browser setup
If your report is already available at a URL and you prefer a hosted capture, ScreenshotNeo accepts one GET request and can return a clean PNG, JPEG, WebP, or PDF. Its capture pipeline accepts cookie and consent banners as a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
Free tools Windows power users keep installed
One-click scans. No signup required.
See the ScreenshotNeo documentation for the current request options. This cURL example captures a published report URL:
curl -G 'https://api.screenshotneo.com/v1/shot' -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/report -o shot.webp
The same request in Python:
import requests
r = requests.get('https://api.screenshotneo.com/v1/shot', params={'access_key': 'YOUR_API_KEY', 'url': 'https://example.com/report'}, timeout=90)
open('shot.webp', 'wb').write(r.content)
And in Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/report' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; the published tiers are Starter ($5/3,000), Growth ($15/15,000), Pro ($39/60,000), Scale ($99/250,000), and Business ($249/1,000,000), with two months free on yearly billing. Every feature is included on every plan. Create a free ScreenshotNeo account to try it without a card.
Frequently Asked Questions
Will the repeated heading appear in the normal browser view?
Not necessarily. The recommended rule is inside @media print, so it controls PDF and print output without changing the screen layout.
Can I use a multi-row table header?
Yes. Put each header row in the same <thead>; Chromium treats that section as one table-header group when it paginates the table.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

