HTML Table to PDF: Complete Guide for Print, Code, and Automation
Convert an HTML table to PDF with browser print, CSS, Puppeteer, Python, or an API. Fix page breaks, wide tables, fonts, and automation errors.
To convert an HTML table to PDF, use your browser’s Print command and choose Save as PDF. If the table is too wide or breaks awkwardly, add print-specific CSS, choose landscape orientation, adjust paper size and margins, and control page breaks. For repeatable conversion, render the page with a headless browser such as Puppeteer, which uses the print CSS media type by default. MDN explains print styles, and the Puppeteer Page.pdf documentation describes browser PDF output.
1. Fastest method: Print the table to PDF
- Open the page containing the table in Chrome, Edge, Firefox, or another browser.
- Press Ctrl+P on Windows/Linux or Command+P on macOS.
- Choose Save as PDF or the equivalent PDF destination.
- Select the paper size, orientation, margins, scale, and whether backgrounds should print.
- Open the saved PDF and check every page, especially the rightmost columns and rows split across pages.
This is suitable for a one-off export. It preserves the browser’s rendered content, including the table’s current data and styles. It does not create a repeatable pipeline, so use automation when the PDF must be generated on a schedule, for many URLs, or inside an application.
2. Add print CSS before exporting
HTML is flowing content; PDF uses fixed-size pages. A responsive table that looks good in a resizable browser window can be clipped, scaled until unreadable, or split in an inconvenient place on paper. Give print output its own layout with a @media print block.
<!doctype html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Quarterly sales</title>
<style>
* { box-sizing: border-box; }
body { font: 14px/1.4 system-ui, sans-serif; color: #111; }
.screen-only, nav, button { display: block; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #777; padding: 6px 8px; text-align: left; }
thead { background: #eee; }
@media print {
@page { size: A4 landscape; margin: 12mm; }
.screen-only, nav, button { display: none !important; }
body { margin: 0; font-size: 10pt; }
table { width: 100%; table-layout: fixed; }
th, td { overflow-wrap: anywhere; }
thead { display: table-header-group; }
tfoot { display: table-footer-group; }
tr, img { break-inside: avoid; }
h1, h2 { break-after: avoid; }
a { color: inherit; text-decoration: none; }
}
</style>
</head>
<body>
<nav class='screen-only'>Interactive navigation</nav>
<h1>Quarterly sales</h1>
<table>
<thead>
<tr><th>Region</th><th>Orders</th><th>Revenue</th></tr>
</thead>
<tbody>
<tr><td>North</td><td>184</td><td>$42,800</td></tr>
<tr><td>South</td><td>161</td><td>$38,100</td></tr>
</tbody>
</table>
</body>
</html>
What the print rules do
@pagesets paper size, orientation, and margins. Uselandscapefor wide tables and test portrait before committing to it.thead { display: table-header-group; }asks the browser to repeat the header on subsequent pages.break-inside: avoidreduces rows or related blocks being split, although a row taller than a page cannot remain intact.- Hiding navigation, buttons, cookie controls, and interactive widgets keeps the PDF focused on the data.
table-layout: fixedmakes column widths predictable. Add explicit widths to important columns when necessary.
Do not shrink the entire document until text is unreadable. For a wide table, first try landscape paper, smaller margins, shorter column labels, and deliberate column widths. Use a larger paper format only when the intended readers can print it.
3. Convert a page with Puppeteer (Node.js)
Puppeteer renders the page in Chromium and calls page.pdf(). Its documentation states that this method generates a PDF using the print CSS media type by default. Install it in a new project:
mkdir html-table-pdf
cd html-table-pdf
npm init -y
npm install puppeteer
Create convert.js:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com/table.html', {
waitUntil: 'networkidle0',
timeout: 60000
});
await page.emulateMediaType('print');
await page.pdf({
path: 'table.pdf',
format: 'A4',
landscape: true,
printBackground: true,
preferCSSPageSize: true,
margin: { top: '12mm', right: '12mm', bottom: '12mm', left: '12mm' }
});
} finally {
await browser.close();
}
})();
Run it with node convert.js. Replace the URL with a page you control or an accessible page containing the table. If the page needs authentication, establish the session with cookies or headers before calling page.goto.
Wait for data that JavaScript inserts
await page.goto('https://example.com/table.html', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('table tbody tr', { timeout: 30000 });
await page.waitForFunction(() => document.fonts.status === 'loaded');
await page.waitForNetworkIdle({ idleTime: 500, timeout: 30000 });
await page.pdf({ path: 'table.pdf', format: 'A4', printBackground: true });
Use a selector that proves the table is populated, not merely that an empty table element exists. A fixed delay can help with unusual applications, but a condition is usually more reliable.
Useful Puppeteer PDF options
| Option | Purpose |
|---|---|
format |
Standard paper such as A4 or Letter. |
landscape |
Rotates the page for wide tables. |
margin |
Sets top, right, bottom, and left margins. |
scale |
Scales rendered content; keep text legible. |
printBackground |
Includes background colors and images. |
preferCSSPageSize |
Uses a CSS @page size when one is defined. |
pageRanges |
Exports selected pages when you do not need the whole document. |
displayHeaderFooter |
Adds configured header and footer templates. |
Check the generated file rather than assuming an option solved the layout. A table can still overflow if a cell contains an unbreakable URL, a very wide image, or a long code value.
4. Convert HTML to PDF in Python
For a Python workflow, choose a renderer that matches the CSS and browser behavior your table needs. xhtml2pdf documents fixed-size pages, templates, and frames; verify its supported layout against your exact CSS before adopting it.
python -m pip install xhtml2pdf
from pathlib import Path
from xhtml2pdf import pisa
html = Path('table.html').read_text(encoding='utf-8')
with open('table.pdf', 'wb') as output:
result = pisa.CreatePDF(src=html, dest=output)
if result.err:
raise RuntimeError(f'PDF conversion failed with {result.err} errors')
If the page depends heavily on modern browser CSS, client-side JavaScript, web fonts, or dynamic data, use a browser renderer such as Puppeteer instead. A non-browser converter may require simpler markup or a renderer-specific stylesheet.
5. Client-side export with html2pdf.js
html2pdf.js runs in the browser and combines html2canvas with jsPDF. It is convenient for exporting a selected element without a server, but its documented workflow renders the content as an image inside the PDF. Text may therefore not be selectable or searchable, and files can be larger.
<button id='save'>Save table as PDF</button>
<div id='report'>...your table...</div>
<script src='https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js'></script>
<script>
document.querySelector('#save').addEventListener('click', () => {
const element = document.querySelector('#report');
html2pdf().set({
margin: 10,
filename: 'table.pdf',
image: { type: 'jpeg', quality: 0.95 },
html2canvas: { scale: 2, useCORS: true },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'landscape' },
pagebreak: { mode: ['css', 'legacy'] }
}).from(element).save();
});
</script>
Use this when browser-only export and image-rendered output are acceptable. Choose a browser PDF renderer when selectable text, accessibility, or reliable pagination matters.
6. Use a hosted HTML-to-PDF service
A hosted converter can accept a URL or raw HTML and run a headless browser for you. The html2pdf.app documentation notes that media mode, fonts and other resources, and JavaScript timing affect output. Keep API keys on your server, wait for dynamic content, and inspect PDFs from representative pages.
7. Or skip the browser setup
ScreenshotNeo provides a website capture API with PDF support. One GET request targets the page, while its capture pipeline can accept consent banners before capture and remove more than 60 known consent platforms, newsletter popups, and chat widgets. Each cleanup step can be turned off. Use the ScreenshotNeo API documentation for PDF options and the complete parameter list.
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}`);
ScreenshotNeo also supports PDF capture, full-page rendering with lazy images loaded, custom CSS and JavaScript, waiting for selectors or network idle, custom headers and cookies, geolocation and timezone, bulk capture, caching, signed links, async jobs, and an MCP server with take_screenshot, get_page_info, and capture_pdf for AI agents. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and whether the response was billed.
There are 1,000 free shots each month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.
8. Layout checklist for wide or long tables
- Choose landscape before reducing scale.
- Set an explicit paper size and margins.
- Repeat the table header on every page.
- Allow ordinary text to wrap and handle long URLs deliberately.
- Hide navigation, filters, buttons, popups, and chat controls in print CSS.
- Load web fonts before conversion, or use a dependable fallback.
- Wait for asynchronous rows, images, and charts.
- Use
break-insiderules for row groups, but inspect the result when content exceeds one page. - Open the PDF and test text selection, search, links, page order, and the final file size.
9. Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Right columns are missing | Portrait paper or an overflowing fixed-width table. | Use landscape, set table width to 100%, reduce margins, and handle long unbroken values. |
| Rows split in the middle | CSS fragmentation rules cannot keep the content together. | Apply break-inside: avoid to rows or row groups and inspect tall rows that cannot fit. |
| Header appears only once | The table header is not treated as a repeating table group. | Use thead { display: table-header-group; } and verify the renderer supports it. |
| PDF contains an empty table | JavaScript had not populated rows before capture. | Wait for a populated-row selector or an application-ready condition. |
| Fonts or images are missing | Resources were blocked, cross-origin, or still loading. | Use accessible URLs, configure credentials or CORS as needed, wait for fonts and images, and provide fallbacks. |
| Print colors disappear | Background printing is disabled. | Enable background graphics in the print dialog or use Puppeteer’s printBackground: true. |
| html2pdf output is blurry | The element was rasterized at too low a canvas scale. | Increase html2canvas.scale, reduce the capture area, or choose a native browser PDF. |
| Puppeteer times out | The page never reaches the selected load condition or a request hangs. | Use a suitable wait condition, set a bounded timeout, and investigate blocked or slow resources. |
| PDF is too large | Large images, background assets, or rasterized pages. | Resize images, remove unnecessary backgrounds, and prefer selectable-text browser output. |
10. Performance, reliability, and cost considerations
Manual printing has almost no setup cost but does not scale. Browser automation adds browser startup, page loading, font loading, JavaScript execution, and PDF generation time. Reuse a browser process for batches, limit concurrency to the capacity of the host, and close pages after each job. Cache stable assets and avoid waiting for unrelated third-party requests when a table-ready selector is available.
Reliability depends on deterministic input. Pin the page’s styles where possible, make data loading observable, set timeouts, record the source URL and generation time, and keep sample PDFs for regression checks. Treat a successful HTTP response as insufficient: inspect that the table has rows and that the PDF has the expected page count or text.
Cost comes from your compute, a hosted conversion plan, or both. Client-side export shifts work to the user’s device. A hosted service can reduce browser maintenance; check how it bills failed loads, cache hits, and successful captures. ScreenshotNeo identifies verdict and billing status in response headers and charges only clean shots; its free tier includes 1,000 shots per month with no card.
11. FAQ
Can I convert only the table instead of the whole page?
Yes. Put the table in a dedicated print container, hide other content in @media print, or select the element in html2pdf.js. Browser PDF tools can also render a page designed specifically for the table.
Should I use HTML or CSV as the source?
Use HTML when presentation, links, formatting, and print layout matter. Generate a CSV or spreadsheet when the primary requirement is data interchange rather than a paginated document.
Will PDF text remain searchable?
Native browser PDF output generally preserves rendered text. html2pdf.js documents an image-based workflow, so verify text selection and search before using it for archival or accessibility needs.
How do I make a table fit on one page?
Reduce columns, shorten labels, use landscape paper, tighten margins and spacing, or choose a larger paper size. Scaling should be the last adjustment because it can make the result unreadable.
Can a PDF include several table pages?
Yes. Long tables flow across pages. Repeat the header with table-group CSS and inspect where rows and section headings break.


