هذه المقالة متاحة حاليًا باللغة الإنجليزية فقط. الترجمة قريبا.
Odoo prints PDF reports with wkhtmltopdf, which ignores the CSS3 break-before: page. Put page-break-before: always on the element that starts the new page, keep it inside the report's page wrapper, and use page-break-inside: avoid on a table that fits on one page.
You add <div class="page-break"/> to an Odoo report. The PDF renders the content but ignores the break — content flows continuously across what should be page boundaries. Or the inverse: every section forces a new page, including ones that should fit. wkhtmltopdf and web.external_layout interact in subtle ways that make page breaks one of the most asked-about parts of Odoo report design on 17.0/18.0/19.0.
Quick Fix
Use the CSS page-break-before or page-break-after properties, not the <div class="page-break"/> shortcut:
<!-- Before each major section -->
<div style="page-break-before: always"/>
For conditional page breaks (only break if too little space remains), use page-break-inside:
<table style="page-break-inside: avoid">
<!-- table contents stay on one page if possible -->
</table>
Why This Happens
wkhtmltopdf supports CSS print media queries and the page-break-* family of properties, but with quirks:
<div class="page-break"/>is an Odoo convention — Odoo'sweb.external_layoutstyles map this topage-break-before: always. If your template wraps the page-break div incorrectly (outside the<div class="page">wrapper), the style does not apply.page-break-beforeon the first element in a wrapper does nothing — the renderer is already at a page boundary.- Tables and page breaks.
page-break-inside: avoidon a table works only if the table fits on a page; tables larger than one page break wherever fits. - Float and absolute positioning mess with page breaks. Floats can flow off the bottom of one page and reappear at the top of another in unexpected places.
- wkhtmltopdf's outdated WebKit does not support
break-before: page(the modern CSS3 syntax) — onlypage-break-before: always(the legacy syntax).
Step-by-Step Diagnosis
1. Render to HTML to inspect:
http://localhost:8069/report/html/sale.report_saleorder/14?debug=1
In the browser DevTools, look at where page-break-* styles are computed. If the style is not always, the rule did not match.
2. Check wkhtmltopdf's print CSS support.
echo '<html><body><div style="page-break-before: always">Page 2</div></body></html>' > test.html
wkhtmltopdf --print-media-type test.html out.pdf
If the PDF has only one page, wkhtmltopdf is ignoring the break. Try --enable-internal-links and other flags.
3. Inspect Odoo's rendered template. Find where <div class="page-break"/> appears in the composed HTML. It should be a sibling of <div class="page">, not nested inside.
4. Check CSS specificity. A more specific rule may be overriding your page-break:
.row > div { page-break-before: avoid; } /* defeats your "always" rule */
5. Test with a minimum reproduction. A QWeb template with three <div class="page"> wrappers, one page-break between each, and only "Hello" text in each. If this works, the issue is CSS in the rest of your template.
Permanent Fix
For unconditional breaks between sections:
<template id="report_priority_document">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="web.external_layout">
<div class="page">
<h2>Order: <span t-field="o.name"/></h2>
<!-- First-page content -->
<table>...</table>
</div>
</t>
<!-- Force a page break -->
<div style="page-break-before: always"/>
<t t-call="web.external_layout">
<div class="page">
<h2>Continuation: <span t-field="o.name"/></h2>
<!-- Second-page content -->
</div>
</t>
</t>
</t>
</template>
The page break sits between two <t t-call="web.external_layout"> blocks, each rendering its own page header/footer.
For conditional breaks (keep table together if possible):
<table class="table" style="page-break-inside: avoid">
<thead>
<tr><th>Item</th><th>Qty</th><th>Price</th></tr>
</thead>
<tbody>
<tr t-foreach="o.order_line" t-as="line">
<td><span t-field="line.product_id.name"/></td>
<td><span t-field="line.product_uom_qty"/></td>
<td><span t-field="line.price_unit"/></td>
</tr>
</tbody>
</table>
page-break-inside: avoid keeps the table on one page if it fits.
For row-level breaks (break before this row):
<tr style="page-break-before: always" t-if="line.is_section_break">
<td colspan="3"><b><span t-field="line.name"/></b></td>
</tr>
For headers that should repeat on each page:
<table>
<thead style="display: table-header-group">
<tr><th>Item</th><th>Qty</th></tr>
</thead>
<tbody>
<!-- rows -->
</tbody>
</table>
display: table-header-group makes the <thead> repeat on every page wkhtmltopdf renders for the table.
For complex flows, use <div class="page"> per logical page. Odoo's external_layout wrapping each <div class="page"> gives you per-page header/footer plus implicit page boundaries. This is the most reliable structure.
How to Prevent It
- Always test with multi-page data. Reports with one item fit on one page; you only see page-break bugs when there are 50 items. QA with realistic record counts.
- Avoid
<div class="page-break"/>shorthand. Use explicitstyle="page-break-before: always". The shorthand depends on Odoo's CSS, which can change between versions. - Use legacy
page-break-*syntax. wkhtmltopdf 0.12.6 does not supportbreak-before: page(CSS3). Stick withpage-break-before: always. - One
<div class="page">per logical page. If your report has an order summary page and an order detail page, two<div class="page">wrappers is the cleanest structure. - CI test page count. A test that generates a PDF and asserts page count using
pdfinfoor PyPDF catches break regressions. - Print-stylesheet review. When inheriting
web.external_layoutstyles, ensure your CSS does not accidentally setpage-break-before: avoidon elements that should break.
Related Errors
- PDF report renders blank page — adjacent rendering bug.
- wkhtmltopdf protocol error — sibling toolchain failure.
- Report translations missing — content-level report bug.
- QWeb template not found at runtime — pre-render failure.
Frequently Asked Questions
Can I number pages in an Odoo report?
Yes, in web.external_layout footer:
<div class="footer">
Page <span class="page"/> of <span class="topage"/>
</div>
wkhtmltopdf replaces class="page" with the current page and class="topage" with the total at PDF generation time. Standard wkhtmltopdf magic.
Why does page-break-after: always get ignored on the last element?
Because there is no content after it to push to a new page. The renderer correctly drops the break. If you need an empty trailing page (rare), add a hidden div with content after the break.
Can I use modern CSS like break-before: page?
Not safely. wkhtmltopdf 0.12.6's WebKit is from 2014 and does not support the CSS3 fragmentation properties consistently. Use page-break-* syntax for predictable behaviour.
My table breaks in the middle of a row. How do I fix it?
Add style="page-break-inside: avoid" to the <tr>. If rows are too tall to ever fit on a single page, this falls back to row-internal breaks anyway. For uniformity, set the rule on tr globally in your stylesheet:
@media print { tr { page-break-inside: avoid; } }
Can I force a specific page count, like always 3 pages?
Not directly through CSS. The closest pattern is to author three explicit <div class="page"> wrappers and pad them with empty space if needed. For form-style documents (purchase orders, invoices) where you need a fixed structure, this manual approach is more reliable than computed.
How do I add a watermark across all pages?
Use CSS position: fixed with @media print:
.watermark {
position: fixed;
top: 50%; left: 50%;
transform: translate(-50%, -50%) rotate(-30deg);
font-size: 4em; opacity: 0.1; color: red;
z-index: 9999;
}
wkhtmltopdf renders fixed-position elements once per page, exactly the watermark behaviour you want. Inline this CSS in the report's QWeb to keep it self-contained.
Page numbering shows wrong total — why?
<span class="topage"/> is replaced with the total page count after wkhtmltopdf finishes laying out. If your template includes table-of-contents-style placeholders that change page count after replacement, totals drift. Run with --javascript-delay 1000 to give wkhtmltopdf time to settle the layout before computing totals.
Need help with a tricky Odoo error? ECOSIRE's Odoo experts have shipped 215+ modules — get expert help.
بقلم
ECOSIRE Research and Development Teamالكتابة التقنية
يغطي فريق الكتابة التقنية في ECOSIRE موضوعات Odoo ERP والتجارة الإلكترونية عبر Shopify ووكلاء الذكاء الاصطناعي وتحليلات Power BI والأتمتة عبر GoHighLevel وأفضل ممارسات برمجيات المؤسسات. تساعد أدلتنا الشركات على اتخاذ قرارات تقنية مدروسة.
ECOSIRE
قم بتحويل أعمالك باستخدام Odoo ERP
تنفيذ وتخصيص ودعم خبير Odoo لتبسيط عملياتك.
مقالات ذات صلة
تجزئة العملاء المدعومة بالذكاء الاصطناعي: من RFM إلى التجميع التنبؤي
تعرف على كيفية قيام الذكاء الاصطناعي بتحويل تجزئة العملاء من تحليل RFM الثابت إلى التجميع التنبؤي الديناميكي. دليل التنفيذ باستخدام Python وOdoo وبيانات عائد الاستثمار الحقيقي.
الذكاء الاصطناعي لتحسين سلسلة التوريد: الرؤية والتنبؤ والأتمتة
تحويل عمليات سلسلة التوريد باستخدام الذكاء الاصطناعي: استشعار الطلب، وتسجيل مخاطر الموردين، وتحسين المسار، وأتمتة المستودعات، والتنبؤ بالاضطرابات. دليل 2026.
دليل التجارة الإلكترونية للجملة B2B: بوابات المشترين وشروط الدفع الآجل والتسعير المخصص
أنشئ بوابة جملة B2B يستخدمها المشترون فعلاً: تسعير خاص بكل عميل، وشروط دفع آجل وحدود ائتمان، وإتمام شراء بأوامر الشراء والموافقات، وإعادة الطلب من سجل الطلبات.