Skip to content

Odoo QWeb Template Syntax: Complete Reference Guide

DeployMonkey Team · March 23, 2026 12 min read

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:

VariableDescription
lineCurrent item
line_index0-based index
line_valueSame as line (alias)
line_firstTrue if first iteration
line_lastTrue if last iteration
line_oddTrue if odd index
line_evenTrue if even index
line_sizeTotal 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-field in reports and website for proper formatting; use t-esc in 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