In jsPDF with AutoTable, inaccurate tables usually come from four independent decisions being left to defaults: the table’s total width, each column’s width, text overflow behavior, and page-break rules. Measure the usable page width first, set explicit margins, choose deliberate tableWidth and cellWidth values, then configure wrapping or truncation. Treat vertical and horizontal pagination separately, and use each hook at the stage where AutoTable expects it.
1. Confirm the API and package versions
Before changing layout code, record the installed jspdf and jspdf-autotable versions. Option names and invocation styles changed between major releases. Current AutoTable usage is generally:
import jsPDF from 'jspdf';
import autoTable from 'jspdf-autotable';
const doc = new jsPDF();
autoTable(doc, {
head: [['Product', 'Quantity', 'Price']],
body: [['Notebook', '2', '$8.00']]
});
doc.save('report.pdf');
Some historical examples install AutoTable as a plugin and call doc.autoTable(...). Use the API documented by the version actually installed; copying a current option into an old release (or an old option into a current release) can make the setting appear to be ignored.
2. Fix columns that are too wide
Calculate usable page width
The table cannot be wider than the page area between the left and right margins. Set margins explicitly so the geometry is predictable:
#1 Best Overall
const margin = { top: 20, right: 15, bottom: 20, left: 15 };
autoTable(doc, {
margin,
tableWidth: 'auto',
head: [['ID', 'Description', 'Status']],
body: rows
});
tableWidth: 'auto' uses the available page width. Use 'wrap' when the table should be only as wide as its content, or provide a number when you need deterministic geometry in the document’s units:
autoTable(doc, {
margin: { left: 15, right: 15 },
tableWidth: 180,
head: [['ID', 'Description', 'Status']],
body: rows
});
If the sum of your numeric column widths exceeds the available width, AutoTable must compress, wrap, or push content beyond the page. Reduce widths, padding, or font size rather than relying on accidental compression.
Control individual columns
Use columnStyles for stable column geometry. A cell width can be 'auto', 'wrap', or a numeric value:
autoTable(doc, {
margin: { left: 15, right: 15 },
tableWidth: 'auto',
columnStyles: {
0: { cellWidth: 22 },
1: { cellWidth: 'wrap' },
2: { cellWidth: 30 }
},
styles: {
cellPadding: 2,
fontSize: 9
},
head: [['ID', 'Description', 'Status']],
body: rows
});
Give identifiers and short numeric fields fixed widths, and let prose columns wrap. If a long description is the reason the table expands, a numeric width for that column plus line wrapping is more reliable than allowing every column to size itself from its longest value.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #2
3. Decide what happens when text does not fit
Width and overflow are separate settings. Width determines the box; overflow determines the text policy inside that box. Choose one intentionally:
| Value | Result | Use when |
|---|---|---|
linebreak |
Wraps text and increases row height | Every word must remain readable |
ellipsize |
Truncates with an ellipsis | Compact status or index tables |
visible |
Allows text to spill outside the cell | Only when neighboring cells cannot be obscured |
hidden |
Clips text at the cell boundary | When clipping is preferable to spillover |
autoTable(doc, {
styles: {
overflow: 'linebreak',
cellPadding: 2,
fontSize: 9
},
columnStyles: {
0: { cellWidth: 24, overflow: 'ellipsize' },
1: { cellWidth: 90, overflow: 'linebreak' }
},
head: [['Code', 'Notes']],
body: rows
});
Inspect long headers as well as body cells. A header that cannot wrap can make an otherwise correct table look mis-sized.
4. Stop rows and headers splitting badly across pages
Place the table at the right vertical position
startY is measured from the top of the page. Set it after preceding content has been drawn, rather than guessing:
doc.text('Monthly invoices', 15, 25);
autoTable(doc, {
startY: 35,
margin: { top: 15, right: 15, bottom: 20, left: 15 },
pageBreak: 'auto',
rowPageBreak: 'avoid',
head: [['Invoice', 'Customer', 'Total']],
body: rows
});
pageBreak: 'auto' follows normal flow. Use 'avoid' when AutoTable should move the whole table if it can fit on a later page, and 'always' when the table must begin on a new page. rowPageBreak: 'avoid' keeps a row together unless the row itself is taller than a page.
Recommended Free Tools
Repeat the header on every page
In current releases, use showHead: 'everyPage':
autoTable(doc, {
showHead: 'everyPage',
head: [['Invoice', 'Customer', 'Total']],
body: rows
});
Older documentation may call this option showHeader. If showHead has no effect, verify the installed version and its API rather than adding both names at random.
5. Handle tables wider than a page
Wrapping can make a wide table extremely tall. If the columns are genuinely wider than the page, enable horizontal pagination instead of allowing content to run off the right edge:
autoTable(doc, {
horizontalPageBreak: true,
horizontalPageBreakRepeat: [0],
// The documented behavior can be selected for your reading order:
horizontalPageBreakBehaviour: 'afterAllRows',
head: [['ID', 'Name', 'Region', 'Description', 'Owner', 'Updated']],
body: rows
});
Repeat identifier columns such as an ID or name so readers can associate each horizontal segment with the same record. Choose the documented horizontal-break behavior (immediately or afterAllRows) according to whether you want each vertical slice completed before moving to the next slice. Confirm the exact spelling supported by your installed release, because horizontal pagination was added and refined across versions.
6. Put customization in the correct hook
AutoTable recalculates styles while parsing and drawing. A change in the wrong hook may be overwritten:
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsRank #4
didParseCell: normalize values, alter content, or assign parse-time styles.willDrawCell: call native jsPDF methods immediately before drawing, such assetTextColor.didDrawCell: add images, icons, or extra shapes after the cell has been drawn.
autoTable(doc, {
didParseCell(data) {
if (data.section === 'body' && data.column.index === 2) {
data.cell.styles.overflow = 'ellipsize';
}
},
willDrawCell(data) {
if (data.section === 'body' && data.column.index === 2) {
doc.setTextColor(180, 40, 40);
}
},
didDrawCell(data) {
// Add an image or shape using data.cell.x, y, width and height.
},
head: [['Item', 'Owner', 'Status']],
body: rows
});
7. Make HTML input deterministic
When using the html option, verify the selector, hidden rows and columns, and the text AutoTable actually receives. Browser CSS is not a complete substitute for AutoTable’s layout options. For difficult tables, pass explicit head, body, and columns data so widths and values are controlled by your PDF code:
autoTable(doc, {
columns: [
{ header: 'ID', dataKey: 'id' },
{ header: 'Description', dataKey: 'description' }
],
body: records,
columnStyles: {
id: { cellWidth: 24 },
description: { cellWidth: 120, overflow: 'linebreak' }
}
});
This also makes it easier to detect empty values, unexpected HTML text, and unbroken strings such as URLs or hashes.
8. A repeatable diagnostic checklist
- Log the installed
jspdfandjspdf-autotableversions and confirm the invocation style. - Set explicit left, right, top, and bottom margins; calculate the usable width.
- Choose
tableWidth:'auto','wrap', or a numeric value. - Assign critical columns numeric,
'auto', or'wrap'cellWidthvalues. - Select an overflow policy and test long headers, long words, and empty cells.
- Set
startY,pageBreak,rowPageBreak, andshowHeadfor vertical flow. - Use
horizontalPageBreakand repeated identifier columns for genuinely wide data. - Move content and style changes to the appropriate hook.
- Render and inspect the first page, a page containing a split, the last page, the widest column, and a long unbroken string.
9. Common failures and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Rightmost column is cut off | Total widths exceed usable page width | Reduce numeric widths, use wrapping, or enable horizontal pagination. |
| Text disappears | hidden overflow or a narrow fixed width |
Use linebreak or ellipsize; widen the column. |
| Rows split in the middle | Default row pagination | Set rowPageBreak: 'avoid'; shorten content if a row exceeds a page. |
| Header appears only once | Header repetition not enabled, or legacy option mismatch | Use showHead: 'everyPage' in a compatible release. |
startY overlaps text |
Position was guessed or preceding content changed | Set it after the preceding content’s actual baseline and margins. |
| Hook styling is overwritten | Native calls made during parsing or after AutoTable resets styles | Use didParseCell, willDrawCell, or didDrawCell according to timing. |
| HTML table differs from the browser | Selector, hidden content, or CSS parsing mismatch | Check extracted text and switch to explicit data for deterministic geometry. |
10. Test the rendered PDF, not just the source table
A browser preview can hide PDF-specific problems. Test realistic extremes: the longest header, the longest unbroken token, the largest row, an empty value, a table beginning near the page bottom, and enough rows to create several pages. Compare the generated PDF at 100% zoom and verify that repeated headers, horizontal segments, and clipped text match your intended reading order.
Or skip the browser setup
If your goal is a clean image or PDF of a web page rather than a programmatically composed jsPDF table, ScreenshotNeo provides a single screenshot API request. It accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.
Use the API documentation at https://screenshotneo.com/docs/. cURL:
Best Value
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
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)
Node.js:
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 offers an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Sign up free to try it.
Frequently Asked Questions
Why does changing cellWidth not change the table’s overall size?
cellWidth controls individual cells or columns; tableWidth controls the table box. Set both when you need predictable geometry.
Should I use wrapping or horizontal pagination?
Wrap when preserving every column on one page is more important than row height. Use horizontal pagination when columns are intrinsically wider than the page and should remain readable.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Why is my header option ignored?
Check the installed AutoTable version. Current releases use showHead; older examples may use showHeader.
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.

