An HTML to PDF page-break is not an API flag. RelayPDF prints your HTML with Chromium. Chromium paginates with CSS Fragmentation: break-inside, break-before, break-after, plus widows and orphans. Tables that split mid-row are almost always missing those rules, or they have a display reset that turned the table into a block. There is no avoid_splitting_rows field on POST /v1/pdf. Product page: https://relaypdf.com/html-to-pdf. Print options that do exist (format, margin, preferCSSPageSize) are on https://relaypdf.com/docs/options.
What Chromium actually does
On screen, a table is one tall box. In print, Chromium slices that box into page-sized fragments. The default algorithm will cut through a <tr>, a heading, or a card if that is where the remaining page height ends. That is correct per the spec and ugly on an invoice.
You fix it in the HTML you send as the html field (UTF-8 JSON string, not Base64). The same stylesheet applies if the source is markdown or a Handlebars templateId: those become HTML, then Chromium prints. options.format, options.margin, and options.preferCSSPageSize change the page box. They do not choose where rows break. preferCSSPageSize honors @page size in CSS; it does not invent extra break properties.
| CSS | Use in print |
|---|---|
| break-inside: avoid | Keep a row, card, or figure on one page if it fits |
| break-before: page | Start a new page before this element |
| break-after: avoid | Do not leave a heading alone at the foot of a page |
| thead + table-header-group | Repeat column titles on each page the table spans |
| orphans / widows | Minimum lines of a paragraph at the bottom / top of a page |
The older page-break-inside, page-break-before, and page-break-after names are aliases. Chromium maps them. New stylesheets should use the short names. You can set both if the same HTML is also printed by an older engine. RelayPDF does not add a third vocabulary.
Keep a row or card whole: break-inside: avoid
Put break-inside: avoid on the element that must stay together — a table row, a totals block, a product card. If the remaining space on the current page is shorter than that element, Chromium moves the whole element to the next page and leaves empty space at the bottom. That empty band is the intended result, not a bug.
Do not put break-inside: avoid on a block taller than one page. Chromium cannot honor it. It will push the block to the next page (often leaving a nearly blank page), then split it anyway. Apply the rule to rows and short sections, not to the entire <table> or the entire invoice.
Flex and grid containers often ignore fragmentation. If a card wrapper is display:flex and the rule does nothing, wrap the card in a block element and put break-inside: avoid on that wrapper. Same for overflow:hidden parents: they can clip or confuse the fragmentainer. Keep print layout in normal flow.
Tables: rows, thead, display
Three markup rules, all standard CSS table model:
- Use <thead>, <tbody>, and real <tr> / <th> / <td>. A pile of <div>s styled as a grid will not get table pagination.
- Do not set table { display: block }. Screen-only responsive resets do that so columns stack. In print they destroy table-header-group and row fragmentation. Restore display:table (and display:table-header-group on thead) inside @media print.
- Put break-inside: avoid on tr, not on table. The table is supposed to split; the row is not.
thead repeats at the top of each page the table continues onto when it is a table-header-group. That is the default display for <thead>. If a reset changed it, set it back. tfoot as table-footer-group can repeat as well; treat that as Chromium table behavior, not a RelayPDF option. rowspan and colspan still have to describe a valid table. If a rowspan group must stay together, wrap those rows in their own <tbody> and put break-inside: avoid on that tbody — only if the group fits on one page.
table-layout: fixed plus explicit column widths keeps columns aligned across page fragments. Without that, a long word in a cell on page two can reflow a column and make the continued table look like a different grid.
Headings, widows, orphans
h2, h3 { break-after: avoid; } keeps a heading with the first block that follows it. Without that, you get a title as the last line on a page and the paragraph on the next.
orphans is the minimum number of lines that must remain at the bottom of a page before a paragraph is allowed to break. widows is the minimum that must appear at the top of the next page. p, li { orphans: 3; widows: 3; } is the usual print default people forget to set. These properties apply to block containers of text, not to table rows. They will not stop a <tr> from splitting; that is still break-inside on the row.
break-before: page is the forced chapter break: a new statement, a new attendee packet, a cover then body. Use it on a real container. Do not sprinkle it on every section of a flowing invoice.
A print stylesheet that is enough
Drop this in the HTML you POST. It is Chromium print CSS. It is not a RelayPDF extension.
@media print {
h2, h3 { break-after: avoid; }
p, li { orphans: 3; widows: 3; }
table { display: table; table-layout: fixed; width: 100%; }
thead { display: table-header-group; break-inside: avoid; }
tr, .card { break-inside: avoid; }
}
Print in Chrome first (Save as PDF) with the same HTML and page size. Then POST /v1/pdf. If preview is clean and the API PDF is not, check margin, headerTemplate height, or waitUntil — not a second break engine.
What to send on the request
POST https://api.relaypdf.com/v1/pdf with exactly one source. For this recipe, html. Auth is a bearer key. Failed renders return render_failed and are not billed. Contract: https://relaypdf.com/docs/pdf.
curl https://api.relaypdf.com/v1/pdf
-H "Authorization: Bearer pdf_live_..."
-H "Content-Type: application/json"
-d '{
"html": "<style>@media print{tr{break-inside:avoid}thead{display:table-header-group}p{orphans:3;widows:3}}</style>
<table><thead><tr><th>Item</th><th>Qty</th></tr></thead>
<tbody><tr><td>Widget</td><td>2</td></tr></tbody></table>",
"filename": "lines.pdf",
"options": {
"format": "letter",
"printBackground": true,
"margin": { "top": "16mm", "right": "12mm", "bottom": "16mm", "left": "12mm" }
}
}'
--output lines.pdf
If you also set headerTemplate / footerTemplate, increase top and bottom margin so the chrome does not sit on the last row. That collision looks like a bad page-break. It is a clipped margin. Details: https://relaypdf.com/blog/html-to-pdf-headers-footers.
printBackground defaults to true, so striped rows survive. A print media query that drops backgrounds still yields a white table. Fix the CSS.
What this is not
- Not a RelayPDF-only property. If a blog tells you to send page_break_mode or avoidTableSplit in JSON, that is another vendor. Ours does not have those keys.
- Not CSS Paged Media running elements or Prince-quality fragmentation. Chromium is Chromium. We do not claim named pages, margin boxes as body headers, or guaranteed keep-together on flex.
- Not a reason to put overflow:scroll on the table and hope the PDF grows. The PDF page height is format + margin, not the scrollport.
- Not wkhtmltopdf. RelayPDF can convert with that engine on a different path; this post is the Chromium html job. WebKit thead repeat is historically weaker. Do not mix advice.
When the table is the document
Long invoices should split. The failure is a row cut through a description, or page two with no column titles. break-inside on tr plus a real thead is the fix. Invoice HTML versus templateId: https://relaypdf.com/blog/generate-invoice-pdf-api.
Send HTML at https://relaypdf.com/html-to-pdf. Options: https://relaypdf.com/docs/options. Endpoint: https://relaypdf.com/docs/pdf. “Page 3 of 12” in the margin is headerTemplate, not fragmentation.