Odoo QWeb Report Snippet Generator
The boilerplate and the gotchas — inheritance xpaths, null-safe fields, and PDF pagination — in one place.
Scaffold a new report
<record id="action_sale_order_report" model="ir.actions.report">
<field name="name">Quotation</field>
<field name="model">sale.order</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">sale_custom.sale_order_report</field>
<field name="report_file">sale_custom.sale_order_report</field>
<field name="paperformat_id" ref="base.paperformat_euro"/>
</record><template id="sale_order_report">
<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>
<!-- Add your report content here -->
</div>
</t>
</t>
</t>
</template>The basics
What is QWeb, and why do reports break so often?
QWeb is Odoo's
XML templating language — the same engine that renders website pages
also renders every PDF/HTML report, built from directives like
t-field, t-if,
t-foreach and t-call.
Reports fail in a small number of very recurring ways: a
t-field reaching into an empty related
record, an xpath inheritance that assumed the page would never
change shape, and PDF pagination splitting content in the wrong
place. This tool targets exactly those four situations instead of
being a general-purpose QWeb editor.
Getting started
How to use this tool
Scaffold a brand-new report from a model name — get a matching
ir.actions.report record and a QWeb
template with the right layout and paper format wired up.
The most common real task: inherit an existing report (pick from common core reports or paste any external id), supply an xpath, and this tool warns you if that xpath looks fragile.
Pick a field and its display widget (monetary, contact, image...)
and get the correct t-field/
t-options pair, optionally guarded
against the classic empty-value crash.
Three ready-to-copy CSS patterns for the single most-reported QWeb problem: PDF pagination splitting rows, ignoring page breaks, or losing the table header on later pages.
Reference
Field widget reference
What t-field generates for each display
widget the Field Snippet tab supports.
<span t-field="doc.name"/> <span t-field="doc.amount_total" t-options='{"widget": "monetary", ...}'/> <span t-field="doc.date_order"/> <span t-field="doc.partner_id"/> <div t-field="doc.partner_id" t-options='{"widget": "contact", ...}'/> <img t-att-src="image_data_uri(doc.image)"/> <span t-field="doc.description"/> <span t-field="doc.active"/> Reference
Common core reports to extend
The external ids behind Odoo's most frequently customized reports — also available as quick-select buttons in the Extend Report tab.
sale.report_saleorder_document account.report_invoice_document stock.report_deliveryslip stock.report_picking purchase.report_purchaseorder_document Extending reports safely
Strong xpath vs. fragile xpath
An inheriting <xpath> is only as
durable as the selector it's anchored to. A selector with no
attribute predicate matches whatever happens to be in that position
today — and silently breaks, or silently targets the wrong element,
the moment core or another module reorders the page.
//div[@name='so_total_summary'] Strong Strong — anchored to a name attribute the core module explicitly kept stable across versions.
//div[3]/table/tr[2] Fragile Fragile — a positional path with no predicate. Any module that adds, removes, or reorders a div before this one silently breaks it.
//span[@t-field='doc.partner_id'] Strong Strong — anchored to the field being rendered, which rarely changes even when surrounding markup does.
Reference
Page break & pagination patterns
PDF pagination (wkhtmltopdf/WeasyPrint) is the single most reported QWeb report problem — these three CSS patterns, live in the Page Breaks tab above, cover almost every case.
<p style="page-break-before: always;"></p> Insert this between iterations of a t-foreach (guarded so it never fires before the first item) to start each one on its own page — e.g. one page per invoice when printing several at once.
<div style="page-break-inside: avoid;">
<!-- content that must stay together -->
</div> Wrap a table row, a heading with its paragraph, or any block that should never be cut in half at a page boundary.
<thead style="display: table-header-group;">
<tr><th>...</th></tr>
</thead> table-header-group is the standard CSS value for <thead> — this just makes sure nothing in your report's CSS overrides it, so a long table's header repeats on every printed page.
FAQ
Frequently asked questions
Why do I get "'NoneType' object has no attribute" when rendering a report?
The most common QWeb report crash. It happens when t-field (or a t-esc expression) touches a related record that's empty — e.g. doc.partner_id.name when partner_id isn't set. Wrap the block in <t t-if="doc.partner_id">...</t>, or use this tool's Field Snippet tab with 'Guard against empty values' checked, which generates that guard for you automatically.
Why does t-field do nothing when I put it on a <t> tag?
t-field renders by replacing the CONTENT of a real HTML element (it needs a DOM node to attach formatting/widget behavior to). Put it on a <span>, <div>, or similar — never on a bare <t>, which has no element of its own in the rendered output.
Why did my xpath inheritance break after an Odoo upgrade or another module's update?
Positional xpaths (//div[3], //table/tr[2]) silently point at whatever happens to be third or second after any module reorders the page — no error, just the wrong spot or a crash. Anchor to a stable attribute instead: a name, an id, a field, or a distinctive class the core template is unlikely to rename. This tool's Extend Report tab flags any xpath with no attribute predicate.
How do I find the external ID of the report I want to extend?
Enable developer mode, open Settings > Technical > Reports (or print the document and check 'Bug' > download the source), and look up the ir.actions.report / template pair — or check the module's report/*.xml file directly on GitHub. The common reports listed in the Extend Report tab's quick-select cover the most frequently extended ones (sale, invoice, delivery, transfer, purchase).
What's the difference between t-field, t-esc/t-out, and t-raw?
t-field is model-aware: it applies the field's widget/formatting (currency, dates, translations). t-esc (and its modern alias t-out) prints any Python expression as escaped text with no formatting. t-raw prints unescaped HTML and is unsafe with user-controlled data — avoid it unless the content is trusted and already-sanitized HTML.
Why does my table row get split across two PDF pages?
wkhtmltopdf/WeasyPrint paginate strictly by page height with no per-row awareness by default. Wrap the row (or block) in page-break-inside: avoid; — see the 'Don't split this block' snippet on the Page Breaks tab.
How do I repeat my table header on every printed page?
Use display: table-header-group on the <thead> — the browser/PDF engine reprints it at the top of every new page automatically. See the "Repeat a table header" snippet on the Page Breaks tab.
How do I force a section to always start on a new page?
Insert an empty element with page-break-before: always right where the new page should start (a section title, an appendix, a second invoice copy). See the "Force a new page" snippet on the Page Breaks tab.
Why is my QWeb PDF report slow to generate or timing out?
Large t-foreach loops over many-to-many/one2many fields (e.g. hundreds of order lines) each re-evaluate their own field widgets and sub-queries — this is the most common report performance complaint. Prefetch related fields on the reporting model, keep loops flat rather than deeply nested, and avoid calling compute methods per-row inside the template.
My report shows the wrong company's data in a multi-company setup — why?
QWeb reports don't automatically filter by the current company unless the underlying recordset or a t-if="doc.company_id == user.company_id"-style guard does it. Multi-company records (bank details, fiscal position, letterhead) need an explicit company check in the template, not an implicit one.