On this page
Odoo QWeb: fixing PDF page breaks with real examples
Short answer: Tables get sliced mid-row on page breaks because wkhtmltopdf (Odoo's PDF engine) cuts content by page height. Put page-break-inside: avoid on rows or groups that must stay intact, wrap tables in div elements, and always test with real multi-page data.
Environment
- Odoo 17 / 18 / 19 (all use wkhtmltopdf)
- QWeb PDF reports (standard A4
report.paperformat) - Edit templates under Settings > Technical > Reports, or inherit them in a custom module
Symptoms
- Table rows (
tr) cut in half across pages — top half on one page, bottom half on the next - Table borders missing on some sides after the break point
- Table headers not repeated on new pages, making them unreadable
Working example
Recommended structure: let Odoo handle the report header/footer, wrap table content in div elements that forbid internal breaks:
<t t-name="my_module.report_quotation_custom">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<div class="page">
<h2>Quotation <span t-field="doc.name"/></h2>
<div class="order-lines">
<table class="table table-sm">
<thead>
<tr>
<th>Description</th>
<th>Qty</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr t-foreach="doc.order_line" t-as="line">
<td><span t-field="line.name"/></td>
<td><span t-field="line.product_uom_qty"/></td>
<td><span t-field="line.price_subtotal"/></td>
</tr>
</tbody>
</table>
</div>
<div class="totals">
<p>Total: <span t-field="doc.amount_total"/></p>
</div>
</div>
</t>
</t>
</t>
</t>
CSS to add to the template (via the report style or the module's assets):
.order-lines table {
width: 100%;
border-collapse: collapse;
}
.order-lines tr {
page-break-inside: avoid;
}
.totals {
page-break-inside: avoid;
}
Why this works
page-break-inside: avoidontr— tells the engine never to split a row; if a row does not fit, the whole row moves to the next page.divwrappers around content groups — wkhtmltopdf computes page breaks on block elements far more reliably than on bare tables.- A real
thead— not a plain row; the engine tries to repeat it on new pages. - Totals kept as one block —
.totalsis a unit that must never split.
Common mistakes
- Putting
page-break-inside: avoidon the wholetable— a long table jumps to a new page as one block, leaving blank pages. Apply it totror small groups instead. - Deeply nested
divlayers — wkhtmltopdf miscalculates heights. Keep the structure as flat as possible. - Testing with only 2–3 rows — break problems only appear with large data. Always test with 20+ order lines.
- Forgetting about fonts — font fallback changes line heights, so break positions differ from the preview. Install the needed fonts on the server too.
- Editing Odoo's core template directly — upgrades wipe it out. Inherit the template (
t-inherit) in your own module.
Pre-delivery checklist
- Print the PDF from real multi-page data and inspect every break point.
- No row split in half, no missing borders.
- Table headers readable on every page.
- Totals stay together on one page.
References
- Odoo QWeb Templates: https://www.odoo.com/documentation/master/developer/reference/frontend/qweb.html
Related notes
- Odoo QWeb Table Border: controlling PDF table lines on every page
- Odoo Automated Action: Fix forbidden opcode STORE_ATTR with record.write()
- All Odoo notes