What Is QWeb?
QWeb is Odoo's primary template engine. It processes XML templates with special t- directives to produce HTML output. QWeb is used for PDF reports, website pages, email templates, kanban card templates, and backend dashboard components.
Core Directives
t-if / t-elif / t-else
Conditional rendering:
<t t-if="order.state == 'sale'">
<span class="badge bg-success">Confirmed</span>
</t>
<t t-elif="order.state == 'draft'">
<span class="badge bg-warning">Draft</span>
</t>
<t t-else="">
<span class="badge bg-secondary">Other</span>
</t>t-foreach / t-as
Loop over collections:
<table>
<t t-foreach="order.order_line" t-as="line">
<tr>
<td t-esc="line.product_id.name"/>
<td t-esc="line.product_uom_qty"/>
<td t-esc="line.price_unit"/>
<td t-esc="line.price_subtotal"/>
</tr>
</t>
</table>Loop variables available inside t-foreach:
| Variable | Description |
|---|---|
| line | Current item |
| line_index | 0-based index |
| line_value | Same as line (alias) |
| line_first | True if first iteration |
| line_last | True if last iteration |
| line_odd | True if odd index |
| line_even | True if even index |
| line_size | Total collection size |
t-esc vs t-out vs t-raw
<!-- t-esc: HTML-escaped output (safe) -->
<span t-esc="partner.name"/>
<!-- t-out: same as t-esc in Odoo 17+ -->
<span t-out="partner.name"/>
<!-- t-raw: unescaped HTML (use with caution!) -->
<div t-raw="record.description_html"/>Always use t-esc for user-provided data to prevent XSS. Use t-raw only for trusted HTML content like email templates or rich text fields.
t-field
Renders a field with its proper widget and formatting:
<!-- Renders with currency formatting -->
<span t-field="order.amount_total"/>
<!-- With widget options -->
<span t-field="order.amount_total"
t-options="{'widget': 'monetary', 'display_currency': order.currency_id}"/>
<!-- Date formatting -->
<span t-field="order.date_order" t-options="{'format': 'dd/MM/yyyy'}"/>t-field is editable in the website editor and respects field access rights. Use it in website templates and PDF reports for proper formatting.
t-set
Define variables:
<t t-set="total" t-value="sum(line.price_subtotal for line in order.order_line)"/>
<span t-esc="total"/>
<!-- Set with body content -->
<t t-set="address">
<div>
<span t-field="partner.street"/><br/>
<span t-field="partner.city"/>, <span t-field="partner.zip"/>
</div>
</t>
<div t-out="address"/>t-att / t-attf
Dynamic attributes:
<!-- Dynamic attribute from expression -->
<div t-att-class="'alert alert-success' if order.state == 'sale' else 'alert alert-warning'"/>
<!-- Format string attribute -->
<a t-attf-href="/order/#{order.id}">View Order</a>
<!-- Multiple dynamic attributes -->
<input t-att-value="partner.email"
t-att-placeholder="'Enter email'"
t-att-disabled="not editable"/>t-call
Include another template:
<!-- Call a sub-template -->
<t t-call="my_module.address_block"/>
<!-- Call with variables -->
<t t-call="my_module.product_card">
<t t-set="product" t-value="line.product_id"/>
<t t-set="show_price" t-value="True"/>
</t>Template Inheritance
QWeb templates support XPath-based inheritance:
<template id="sale_report_inherit" inherit_id="sale.report_saleorder_document">
<!-- Add after an element -->
<xpath expr="//div[@id='informations']" position="after">
<div class="custom-info">
<strong>Custom Field:</strong>
<span t-field="doc.x_custom_field"/>
</div>
</xpath>
<!-- Replace an element -->
<xpath expr="//span[@t-field='doc.payment_term_id']" position="replace">
<span t-field="doc.payment_term_id" class="fw-bold"/>
</xpath>
<!-- Add before -->
<xpath expr="//table[@class='table']" position="before">
<p class="text-danger">Important notice</p>
</xpath>
</template>PDF Report Template
<template id="report_custom_document">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<div class="page">
<h2><span t-field="doc.name"/></h2>
<table class="table table-sm">
<thead>
<tr>
<th>Product</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<t t-foreach="doc.order_line" t-as="line">
<tr>
<td t-field="line.product_id.name"/>
<td t-esc="line.product_uom_qty"/>
<td t-field="line.price_unit"
t-options="{'widget': 'monetary', 'display_currency': doc.currency_id}"/>
</tr>
</t>
</tbody>
</table>
</div>
</t>
</t>
</t>
</template>Common Patterns
Conditional CSS Classes
<tr t-att-class="'table-danger' if line.discount > 50 else ''"/>Empty State
<t t-if="not records">
<div class="text-center py-5 text-muted">
<h3>No records found</h3>
</div>
</t>Number Formatting
<span t-esc="'%.2f' % amount"/>
<span t-esc="'{:,.2f}'.format(amount)"/>Gotchas
- t-field vs t-esc: Use
t-fieldin reports and website for proper formatting; uset-escin kanban/list templates - Whitespace: QWeb preserves whitespace — use
<t>tags (invisible) to avoid unwanted spaces - Python expressions: QWeb evaluates Python expressions in directives — keep them simple, move complex logic to the controller
- Debugging: Use
t-log="variable"to print values to the server log during rendering