Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use doc.addPage() to create a subsequent PDFKit page. PDFKit creates the first page automatically unless you set autoFirstPage: false. Repeating a table’s column labels is a separate pagination task: before writing each row, your code must detect whether the row fits, add a page when it does not, draw the header again, and then continue with the row.

PDFKit’s documented APIs provide the page primitives and the pageAdded event, but neither the official table documentation nor the consulted pdfkit-table documentation describes a built-in repeat-header switch. Treat header repetition as application logic, and test it against the exact PDFKit and table-library versions you deploy.

What starts a new page in PDFKit?

Call doc.addPage() before drawing the content that belongs on the next page:

const PDFDocument = require('pdfkit');
const fs = require('node:fs');

const doc = new PDFDocument();
doc.pipe(fs.createWriteStream('report.pdf'));

doc.text('Content on the first page');
doc.addPage();
doc.text('Content on the second page');

doc.end();

The first page is created for you by default. If you construct the document with autoFirstPage: false, create the first page yourself with doc.addPage(). Page options such as size, layout and margins can be supplied to an individual addPage call; otherwise constructor defaults apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Adobe Acrobat Pro | PDF Software | Convert, Edit, E-Sign, Protect | PC/Mac Online Code | Activation Required
  • Create and edit PDFs. Collaborate with ease. E-sign documents and collect signatures. Get everything done in one app, wherever you go.
  • Edit text and images without jumping to another app.
  • E-sign documents or request e-signatures on any device. Recipients don’t need to log in to e-sign.
  • Convert PDFs to editable Microsoft Word, Excel, or PowerPoint documents.
  • Share PDFs for collaboration. Commenting features make it easy for reviewers to comment, mark up, and annotate.

Run code whenever any page is created

For a report title, footer, or other page-wide material, register pageAdded:

doc.on('pageAdded', () => {
  doc.fontSize(9).fillColor('gray').text('Quarterly report');
  doc.fillColor('black');
});

The event fires for pages created explicitly with addPage() and for pages created automatically by another operation. Keep the handler purely drawing-related; calling addPage() inside it can recurse indefinitely. A page-wide label is not the same as a table header: the event does not know which table is active, which columns to draw, or whether the next row will fit.

Why table headers do not automatically repeat

A table that spans pages requires three coordinated actions:

  1. Determine whether the next body row fits in the remaining vertical space.
  2. Create a page at the break.
  3. Draw the column labels at the top of that new page before writing the next row.

The official PDFKit table documentation documents table data, row chaining, styling and cursor placement, but it does not document a native repeat-header option. The pdfkit-table README documents headers and controls such as addPage, pageBreakThreshold and keepRowsTogether; it does not list a repeat-header setting. Do not assume that passing a headers array will redraw those labels on every continuation page. Verify the output produced by the precise package version in your application.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Manual pagination: a complete repeating-header pattern

Manual pagination gives you explicit behavior and avoids relying on an undocumented extension option. The example below draws a simple four-column report, measures each row before writing it, starts a new page when needed, and redraws the header. It uses PDFKit’s cursor and text measurement APIs rather than pretending that every row has a fixed height.

Rank #2
Acrobat Pro | 1-Month Subscription | PDF Software |Convert, Edit, E-Sign, Protect |Activation Required [PC/Mac Online Code]
  • Create and edit PDFs. Collaborate with ease. E-sign documents and collect signatures. Get everything done in one app, wherever you go.
  • Edit text and images without jumping to another app.
  • E-sign documents or request e-signatures on any device. Recipients don’t need to log in to e-sign.
  • Convert PDFs to editable Microsoft Word, Excel, or PowerPoint documents.
  • Share PDFs for collaboration. Commenting features make it easy for reviewers to comment, mark up, and annotate.
const PDFDocument = require('pdfkit');
const fs = require('node:fs');

const rows = Array.from({ length: 70 }, (_, i) => ({
  id: String(i + 1),
  customer: `Customer ${i + 1}`,
  status: i % 3 === 0 ? 'Paid' : 'Pending',
  notes: i % 5 === 0
    ? 'A longer note that wraps onto more than one line.'
    : 'No additional notes.'
}));

const doc = new PDFDocument({
  size: 'LETTER',
  margins: { top: 54, right: 42, bottom: 54, left: 42 }
});
doc.pipe(fs.createWriteStream('table.pdf'));

doc.on('pageAdded', () => {
  // Page-wide content only. Do not call addPage() here.
  doc.fontSize(8).fillColor('#666')
    .text('Sales report', doc.page.margins.left, 24, {
      width: doc.page.width - doc.page.margins.left - doc.page.margins.right,
      align: 'right'
    });
  doc.fillColor('#000');
});

const columns = [
  { key: 'id', label: 'ID', width: 42 },
  { key: 'customer', label: 'Customer', width: 128 },
  { key: 'status', label: 'Status', width: 82 },
  { key: 'notes', label: 'Notes', width: 218 }
];
const cellPadding = 5;
const lineGap = 2;
const headerHeight = 24;
const bottom = () => doc.page.height - doc.page.margins.bottom;

function drawHeader() {
  const x = doc.page.margins.left;
  const y = doc.y;
  doc.save();
  doc.rect(x, y, columns.reduce((n, c) => n + c.width, 0), headerHeight)
    .fill('#e9eef5');
  doc.fillColor('#000').font('Helvetica-Bold').fontSize(9);
  let left = x;
  for (const column of columns) {
    doc.text(column.label, left + cellPadding, y + 7, {
      width: column.width - cellPadding * 2,
      lineBreak: false
    });
    left += column.width;
  }
  doc.restore();
  doc.y = y + headerHeight;
}

function rowHeight(row) {
  doc.font('Helvetica').fontSize(9);
  return Math.max(...columns.map(column => {
    const text = String(row[column.key] ?? '');
    return doc.heightOfString(text, {
      width: column.width - cellPadding * 2,
      lineGap
    });
  })) + cellPadding * 2;
}

function drawRow(row, height) {
  const x = doc.page.margins.left;
  const y = doc.y;
  let left = x;
  doc.font('Helvetica').fontSize(9).fillColor('#000');
  for (const column of columns) {
    doc.rect(left, y, column.width, height).stroke('#c7cbd1');
    doc.text(String(row[column.key] ?? ''), left + cellPadding,
      y + cellPadding, {
        width: column.width - cellPadding * 2,
        lineGap
      });
    left += column.width;
  }
  doc.y = y + height;
}

doc.font('Helvetica-Bold').fontSize(16).text('Sales report');
doc.moveDown(0.8);
drawHeader();

for (const row of rows) {
  const height = rowHeight(row);
  const available = bottom() - doc.y;
  // A row taller than a blank page cannot be kept intact. It will be
  // drawn by PDFKit with its text wrapping; split such rows explicitly
  // if your data can exceed one page.
  if (height > available && doc.y > doc.page.margins.top + headerHeight) {
    doc.addPage();
    drawHeader();
  }
  drawRow(row, height);
}

doc.end();

The important detail is the order: calculate the row height, check the available space, call addPage(), call drawHeader(), then draw the row. Drawing the header only once before the loop leaves continuation pages without column labels. Drawing it from pageAdded can work for a single table, but it couples a global event to table state and becomes ambiguous when a report contains several tables or a page begins with non-table content.

Handling the first page and section breaks

The sample draws a report title and then the first header on the automatically created first page. For a new report section, call doc.addPage() explicitly and invoke that section’s header function. If a section has different dimensions, pass page options:

doc.addPage({ size: 'A4', layout: 'landscape', margins: { top: 48, bottom: 48, left: 36, right: 36 } });

Do not rely on the previous page’s cursor position after changing page settings; set the new section’s starting position deliberately.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Rows that are taller than a page

The simple algorithm keeps a row together. That is appropriate for ordinary records, but a single cell containing a very long paragraph can be taller than the usable page. A guard can detect this case and choose a policy: split the cell’s text across pages, truncate it with an explicit continuation marker, or reject/reshape the record before rendering. Merely adding another page does not make an oversized row fit. Test long unbroken strings, images, and rows with different font sizes.

Using pdfkit-table or another table helper

A table extension can reduce drawing code, but its pagination behavior is version-dependent. The pdfkit-table README shows header definitions, asynchronous usage such as await doc.table(...), an addPage setting for starting a table on a fresh page, and page-break controls including pageBreakThreshold and keepRowsTogether. Those settings describe placement and row handling; they are not documented as a repeated-header feature.

Rank #3
PDF Extra Lifetime - Professional PDF Editor - Best Adobe Acrobat Pro Alternative - Lifetime License for Windows PC
  • Perfect Adobe Acrobat Pro alternative – lifetime license for Windows 10 and 11.
  • EDIT text, images, pages, hyperlinks, designs in PDF documents. ORGANIZE PDFs.
  • READ and Comment on PDFs – Intuitive reading modes & document commenting and mark up tools!
  • CREATE, COMBINE, SCAN and COMPRESS PDFs.
  • FILL forms & Digitally Sign PDFs. Work with Digital certificates

If you use the extension:

  1. Pin the exact package version in your lockfile.
  2. Read that version’s README and source for any header-repeat behavior rather than inferring it from the option name headers.
  3. Generate a fixture with enough rows to cross several pages.
  4. Open every continuation page and verify that labels, borders, column widths and wrapped text remain aligned.

If the helper does not redraw headers, use its row/layout calculations where useful but take control of page creation and header drawing yourself. This has a higher maintenance cost, yet it makes the behavior explicit and testable.

Page events, buffered pages and page numbers

PDFKit normally flushes pages as new pages are created. When you need to revisit already-created pages—for example, to add page numbers—construct the document with bufferPages: true and use switchToPage(), as documented in the getting-started guide:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const doc = new PDFDocument({ bufferPages: true });
// ...write the document...
const range = doc.bufferedPageRange();
for (let i = range.start; i < range.start + range.count; i++) {
  doc.switchToPage(i);
  doc.fontSize(8).text(`Page ${i + 1}`, 0, doc.page.height - 30, {
    align: 'center',
    width: doc.page.width
  });
}
doc.end();

Buffering lets you add later material; it does not calculate table row heights and does not repeat a table header by itself. If you combine it with manual pagination, finish the table’s layout first, then perform the page-number pass.

Common failures and precise fixes

The first page is blank

Cause: the document was created with autoFirstPage: false and no page was added before drawing. Fix: call doc.addPage() before the first drawing operation, or remove that constructor option.

Headers appear on page one only

Cause: the header function runs before the row loop and is never called after a break. Fix: call it immediately after every addPage() that continues the table.

Rank #4
PDF Extra 2024| Complete PDF Reader and Editor | Create, Edit, Convert, Combine, Comment, Fill & Sign PDFs | Lifetime License | 1 Windows PC | 1 User [PC Online code]
  • EDIT text, images & designs in PDF documents. ORGANIZE PDFs. Convert PDFs to Word, Excel & ePub.
  • READ and Comment PDFs – Intuitive reading modes & document commenting and mark up.
  • CREATE, COMBINE, SCAN and COMPRESS PDFs
  • FILL forms & Digitally Sign PDFs. PROTECT and Encrypt PDFs
  • LIFETIME License for 1 Windows PC or Laptop. 5GB MobiDrive Cloud Storage Included.

A header is drawn over the first row

Cause: the header renderer paints at the current cursor but does not advance doc.y. Fix: advance the cursor by the header’s actual height, as drawHeader() does.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Rows overlap the footer or are clipped

Cause: the code assumes a fixed row height or checks the page height without subtracting bottom margins and footer space. Fix: measure wrapped text with the same width and font used for drawing, reserve footer space in the available-height calculation, and test the longest normal row.

The page-added callback keeps creating pages

Cause: the callback calls addPage(), which fires the callback again. Fix: let the callback draw only; perform page creation in the pagination loop.

A helper’s “headers” option does not repeat

Cause: a header declaration is being mistaken for a repeat-on-break feature. Fix: consult the installed version’s documentation, create a multi-page fixture, and implement explicit redrawing when repetition is not documented.

Different page sizes produce inconsistent breaks

Cause: hard-coded coordinates or a cached page height are being reused after an individual addPage changes options. Fix: read doc.page.width, doc.page.height and margins at render time.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
PDF Director 3 PLUS - Edit, Convert, Redact, Protect PDFs, Fill Forms for Win 11, 10, 8.1, 7
  • Full-featured PDF Editor: Edit text in the document
  • Fully convert PDF to Word and Excel and continue editing
  • NEW: Further development of existing functions
  • NEW: Even faster and more user-friendly
  • NEW: Over 75 small improvements in all areas
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Testing checklist for production PDFs

  • Use enough records to create at least three pages.
  • Include wrapped cells, empty values, long words and the tallest expected row.
  • Check that every continuation page starts with exactly one header.
  • Confirm the header does not collide with a title, footer or page-wide event content.
  • Test portrait and landscape layouts if both are supported.
  • Inspect generated PDFs in more than one viewer; rendering differences can expose borderline clipping.
  • Keep a fixture PDF in automated tests and compare page count and key text, not only whether the Node.js process completed.

Or skip the browser setup

ScreenshotNeo is unrelated to PDFKit’s internal paginator, but it can produce a clean screenshot or PDF of a URL when your workflow needs a rendered document rather than a programmatically composed PDF. One GET request returns PNG, JPEG, WebP or PDF. Cookie/consent banners, newsletter popups and chat widgets are removed before capture; bot checks, blank pages and failed loads are not billed. Its MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000 shots.

For API parameters and all capture options, see the ScreenshotNeo documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Try ScreenshotNeo when a hosted page is the source you need to capture, and sign up free for 1,000 screenshots a month with no card.

Frequently Asked Questions

Does pageAdded repeat a table header automatically?

No. It is a general page-creation event. Your handler can draw content, but table-specific row measurement and header placement remain your responsibility.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can bufferPages solve repeated headers?

No. Buffering permits later edits to created pages, such as page numbers. It does not paginate rows or redraw column labels.

Should I split a row that cannot fit on one page?

Choose an explicit policy for oversized rows: split their content, truncate with a continuation marker, or reshape the data. Adding pages alone cannot make a row taller than the usable page fit.

The Bottom Line

doc.addPage() creates the next PDFKit page; reliable repeating headers require a pagination loop that measures each row, adds a page when necessary, redraws the labels, and then writes the row. Use pageAdded for general page-wide material, and verify any table extension’s behavior against the exact version you ship.

Quick Recap

Bestseller No. 1
Adobe Acrobat Pro | PDF Software | Convert, Edit, E-Sign, Protect | PC/Mac Online Code | Activation Required
Adobe Acrobat Pro | PDF Software | Convert, Edit, E-Sign, Protect | PC/Mac Online Code | Activation Required
Edit text and images without jumping to another app.; Convert PDFs to editable Microsoft Word, Excel, or PowerPoint documents.
$239.88
Bestseller No. 2
Acrobat Pro | 1-Month Subscription | PDF Software |Convert, Edit, E-Sign, Protect |Activation Required [PC/Mac Online Code]
Acrobat Pro | 1-Month Subscription | PDF Software |Convert, Edit, E-Sign, Protect |Activation Required [PC/Mac Online Code]
Edit text and images without jumping to another app.; Convert PDFs to editable Microsoft Word, Excel, or PowerPoint documents.
$29.99
Bestseller No. 3
PDF Extra Lifetime - Professional PDF Editor - Best Adobe Acrobat Pro Alternative - Lifetime License for Windows PC
PDF Extra Lifetime - Professional PDF Editor - Best Adobe Acrobat Pro Alternative - Lifetime License for Windows PC
Perfect Adobe Acrobat Pro alternative – lifetime license for Windows 10 and 11.; EDIT text, images, pages, hyperlinks, designs in PDF documents. ORGANIZE PDFs.
$99.99
Bestseller No. 4
PDF Extra 2024| Complete PDF Reader and Editor | Create, Edit, Convert, Combine, Comment, Fill & Sign PDFs | Lifetime License | 1 Windows PC | 1 User [PC Online code]
PDF Extra 2024| Complete PDF Reader and Editor | Create, Edit, Convert, Combine, Comment, Fill & Sign PDFs | Lifetime License | 1 Windows PC | 1 User [PC Online code]
READ and Comment PDFs – Intuitive reading modes & document commenting and mark up.; CREATE, COMBINE, SCAN and COMPRESS PDFs
$99.99
Bestseller No. 5
PDF Director 3 PLUS - Edit, Convert, Redact, Protect PDFs, Fill Forms for Win 11, 10, 8.1, 7
PDF Director 3 PLUS - Edit, Convert, Redact, Protect PDFs, Fill Forms for Win 11, 10, 8.1, 7
Full-featured PDF Editor: Edit text in the document; Fully convert PDF to Word and Excel and continue editing
$29.99

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.