Free tools Windows power users keep installed
One-click scans. No signup required.
Most jsPDF table errors come from treating four separate controls as one: the table’s total width, each column’s width, text overflow, and pagination. Fix them in that order. Measure the page width left after margins, set tableWidth and columnStyles deliberately, choose an overflow policy, then configure vertical or horizontal page breaks. Finally, confirm that your installed jspdf-autotable version supports the option names and hook API in your code.
Start with a version-safe invocation
Record the installed versions before changing layout. Major releases have changed option names and invocation styles, so an example copied from an old README may be silently ignored.
npm ls jspdf jspdf-autotable
Use the current function-style API when your installed release documents it:
import { jsPDF } from 'jspdf';
import { autoTable } from 'jspdf-autotable';
const doc = new jsPDF({ unit: 'mm', format: 'a4' });
autoTable(doc, {
head: [['Product', 'Quantity', 'Notes']],
body: [
['Keyboard', '2', 'Wireless, low profile'],
['Monitor', '1', '27-inch display']
]
});
doc.save('table.pdf');
If your package uses a plugin-installation pattern instead, follow that release’s documented installation method rather than mixing APIs. A table that renders with default settings is not proof that every option is supported.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
#1 Best Overall
Measure the usable page width
Width calculations start with the page’s physical width minus the left and right margins. Set margins explicitly when the default is not part of your design:
const margins = { top: 18, right: 18, bottom: 18, left: 18 };
autoTable(doc, {
margin: margins,
tableWidth: 'auto',
head: [['ID', 'Description', 'Status']],
body: rows
});
tableWidth: 'auto' targets the available page width. Use 'wrap' when the table should be only as wide as its content, or provide a numeric width when deterministic geometry is required. Remember that tableWidth controls the whole table; it does not force individual columns to fit.
Control individual columns with columnStyles
Set a policy for every column that is likely to cause drift. A column can use cellWidth: 'auto', 'wrap', or a numeric width. Numeric widths are useful when a report must line up with another page; 'wrap' gives long text room by increasing the column and can make a table exceed the page.
autoTable(doc, {
margin: { left: 15, right: 15, top: 20, bottom: 20 },
tableWidth: 'auto',
columnStyles: {
0: { cellWidth: 18 }, // identifier
1: { cellWidth: 'wrap' }, // descriptive text
2: { cellWidth: 28 }, // status
3: { cellWidth: 'auto' }
},
head: [['ID', 'Description', 'Status', 'Owner']],
body: rows
});
If fixed widths add up to more than the usable page width, text cannot be made accurate without changing the geometry. Reduce widths, margins, font size, or padding, or use horizontal pagination. Do not expect overflow to solve a table that is physically wider than the page.
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 →Choose an explicit text-overflow policy
Width and overflow are separate decisions. overflow describes what happens after a cell’s text does not fit:
linebreakpreserves the text by wrapping it and increasing row height.ellipsizetruncates the displayed value with an ellipsis.visibleallows text to spill outside the cell.hiddenclips text at the cell boundary.
autoTable(doc, {
styles: {
overflow: 'linebreak',
fontSize: 9,
cellPadding: 2
},
columnStyles: {
0: { cellWidth: 20, overflow: 'hidden' },
1: { cellWidth: 70, overflow: 'linebreak' },
2: { cellWidth: 35, overflow: 'ellipsize' }
},
head: [['Code', 'Description', 'Comment']],
body: rows
});
Inspect long headers as well as body cells. A header that does not wrap can make a column appear wider even when ordinary rows fit.
Keep vertical pagination predictable
Position the table after preceding content with startY, and make the page-break policy explicit. The pageBreak option defines what happens when a table spans more than one page.
doc.text('Monthly inventory', 15, 18);
autoTable(doc, {
startY: 25,
pageBreak: 'auto', // normal flow
rowPageBreak: 'avoid', // keep a row together when possible
showHead: 'everyPage', // repeat the header
margin: { top: 15, right: 15, bottom: 15, left: 15 },
head: [['SKU', 'Item', 'Units']],
body: rows
});
pageBreak: 'auto'lets the table flow normally.pageBreak: 'avoid'moves the table when the whole table can fit on a later page.pageBreak: 'always'starts the table on a new page.rowPageBreak: 'avoid'prevents rows from splitting unless a row is taller than a page.showHead: 'everyPage'repeats the header on each page. Older documentation may call this optionshowHeader; use the name supported by your installed release.
Set startY from the actual end position of the preceding content, not from a hard-coded value that becomes invalid when a title wraps.
Rank #3
Use horizontal pagination for genuinely wide tables
When the required column widths exceed the page, shrinking text can destroy readability. Enable horizontal splitting instead:
autoTable(doc, {
horizontalPageBreak: true,
horizontalPageBreakRepeat: [0],
horizontalPageBreakBehaviour: 'afterAllRows',
margin: { left: 14, right: 14, top: 16, bottom: 16 },
columnStyles: {
0: { cellWidth: 18 },
1: { cellWidth: 55 },
2: { cellWidth: 45 },
3: { cellWidth: 45 },
4: { cellWidth: 45 }
},
head: [['ID', 'Name', 'Region', 'Owner', 'Notes']],
body: rows
});
horizontalPageBreakRepeat repeats identifier columns, such as an ID, on each horizontal segment. Choose the documented behavior, 'immediately' or 'afterAllRows', according to whether you want each horizontal segment completed before moving through the rows. Verify the exact spelling supported by your installed version.
Apply hooks at the correct rendering stage
AutoTable can overwrite styles after a hook runs. Put each change at the stage where it belongs:
didParseCell: normalize content or styles while cell data is being parsed.willDrawCell: call native jsPDF drawing methods immediately before a cell is drawn.didDrawCell: add images or shapes after the cell has been drawn.
autoTable(doc, {
head: [['Status', 'Value']],
body: rows,
didParseCell(data) {
if (data.section === 'body' && data.column.index === 0) {
data.cell.text = String(data.cell.text).trim();
}
},
willDrawCell(data) {
if (data.section === 'body' && data.column.index === 0 && data.cell.text[0] === 'Error') {
doc.setTextColor(180, 0, 0);
}
},
didDrawCell(data) {
if (data.section === 'body' && data.column.index === 1) {
// Draw an image or extra shape using data.cell coordinates here.
}
}
});
Make HTML input deterministic
With html input, verify the selector, hidden rows and columns, and the CSS assumptions that determine the resulting cell text. Browser table appearance is not a reliable prediction of PDF geometry. For difficult layouts, pass explicit head, body, and columns data so widths and ordering are under your control.
Rank #4
autoTable(doc, {
html: '#orders',
includeHiddenHtml: false,
styles: { overflow: 'linebreak' },
tableWidth: 'auto'
});
If a selector returns no table, or a hidden cell contributes unexpected text, switch temporarily to explicit arrays and compare the generated PDF.
A complete correction pattern
import { jsPDF } from 'jspdf';
import { autoTable } from 'jspdf-autotable';
const doc = new jsPDF({ unit: 'mm', format: 'a4' });
const rows = [
['A-100', 'A long description that must remain readable across lines', 'Open'],
['B-200', 'Short description', 'Closed']
];
autoTable(doc, {
startY: 28,
margin: { top: 18, right: 16, bottom: 18, left: 16 },
tableWidth: 'auto',
styles: { fontSize: 9, cellPadding: 2, overflow: 'linebreak' },
headStyles: { fontSize: 9 },
columnStyles: {
0: { cellWidth: 25 },
1: { cellWidth: 95, overflow: 'linebreak' },
2: { cellWidth: 35, overflow: 'ellipsize' }
},
pageBreak: 'auto',
rowPageBreak: 'avoid',
showHead: 'everyPage',
head: [['Code', 'Description', 'Status']],
body: rows
});
doc.save('corrected-table.pdf');
Render and inspect the first page, a page containing a split, the last page, the widest column, and rows containing long unbroken strings. Compare the PDF itself, not just the source HTML.
Common symptoms and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Columns run off the page | Fixed widths exceed usable width, or wrapping expanded a content-width table. | Measure margins; use tableWidth: 'auto', numeric widths that fit, smaller padding/font, or horizontal pagination. |
| Text is cut off | hidden or a narrow fixed cell. |
Use linebreak for preservation, widen the cell, or choose ellipsize when truncation is acceptable. |
| Text spills into another column | visible overflow. |
Switch to linebreak, ellipsize, or hidden deliberately. |
| Rows split awkwardly | Default row pagination or an incorrect startY. |
Set rowPageBreak: 'avoid' and calculate startY after preceding content. |
| Header appears only once | Header repetition is disabled or the legacy option name was copied. | Use showHead: 'everyPage' in current releases; verify whether an older release expects showHeader. |
| A hook change disappears | The hook runs before AutoTable applies its drawing styles. | Move normalization to didParseCell, native styling to willDrawCell, and additions to didDrawCell. |
| HTML table differs from the PDF | Selector, hidden content, or CSS parsing changed the cell data. | Check the selector and parsed text, then use explicit head, body, and columns. |
| Option has no effect | Version/API mismatch. | Run npm ls jspdf jspdf-autotable and consult the documentation for that installed release. |
Performance, reliability, and cost considerations
- Wrapping long text increases row height and can create many pages; ellipsizing reduces output size but changes the displayed content.
- Explicit arrays avoid the selector and CSS variability of HTML parsing and make repeated renders easier to compare.
- Horizontal pagination preserves readable column widths but requires readers to move across segments; repeat a stable identifier column so segments remain understandable.
- Always test a normal row, a very tall row, a long unbroken token, and the widest set of columns. A layout that works with short sample data can fail when production text expands.
Or skip the browser setup
If your goal is to capture a rendered page or PDF rather than generate a jsPDF table in the browser, ScreenshotNeo provides a single 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; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
Install the setup described in the ScreenshotNeo documentation, then make a call:
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemscurl -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 full-page lazy-image capture, CSS-selector element capture, device presets or custom viewports, retina scale, PDF paper and page-range controls, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous webhooks, bulk capture of 100 URLs per call, usage data, an OpenAPI specification, and familiar parameter names for easier migration. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
Best Value
Final verification checklist
- Installed
jspdfandjspdf-autotableversions are recorded. - Left and right margins are included in the width calculation.
tableWidthand every importantcellWidthfit the intended geometry.- Overflow behavior is intentional for headers, body cells, and long tokens.
startY,pageBreak,rowPageBreak, and header repetition match the report flow.- Horizontal splitting and repeated identifier columns are enabled for wide tables.
- Hooks are placed according to parse, pre-draw, and post-draw timing.
- The actual PDF has been inspected at page boundaries and with production-length data.
Frequently Asked Questions
Can I make every column a fixed width?
Yes, but the widths must fit the page after margins. If their sum is larger, reduce widths or use horizontal pagination; overflow settings cannot remove excess physical width.
Why does a wrapped row make the next page look almost empty?
linebreak increases row height. With rowPageBreak: 'avoid', AutoTable moves a row that cannot fit, so a large row can leave unused space on the preceding page.
Should I use HTML parsing for a complex report?
Use explicit head, body, and columns when geometry, hidden content, or CSS interpretation must be deterministic. HTML input is convenient but requires selector and parsed-text checks.
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.

