Odoo’s QWeb templating system is the core for building front-end views, reports, website pages, and various dynamic HTML/XML outputs. In Odoo 19, QWeb has matured further, offering more operations and directives to control rendering, caching, conditions, loops, inheritance, sub-templates, and more.
In this article, we’ll walk through the most important QWeb features and operations in Odoo 19: how to output data, use conditionals, loops, attributes, variables, sub-templates, caching, and some advanced/custom operations. By the end, you should be comfortable authoring or customizing QWeb templates in Odoo.
What is QWeb?
QWeb is the templating engine used in Odoo for rendering HTML/XML from templates with embedded directives. It is used in:
- Website frontends and portal pages
- The website builder
- Email templates
- PDF and Excel reports
- Backend UI components
Templates are defined with t- directives (attributes prefixed with t-) and special tags. The template is parsed and rendered, resolving variables, loops, and conditions dynamically.
Basic QWeb Templates
A basic QWeb template defines reusable HTML/XML markup that can display dynamic data passed from Odoo models or controllers.
<template id="hello_template">
<div>
<h2>Hello, <t t-esc="user.name"/>!</h2>
</div>
</template>
Templates can be rendered from controllers, reports, or views, making them highly flexible across Odoo’s ecosystem.
Basic Data Output
To insert data into a QWeb template, you use:
- t-esc — for escaped output (default; safe against HTML injection)
- t-raw — for raw/unescaped output (use carefully)
- t-out — older/deprecated directive; use t-esc or t-raw instead.
<t t-esc="partner.name"/>
<t t-raw="partner.description"/>
Also, there is support for “smart record fields formatting,” where QWeb automatically handles formatting of Odoo fields (dates, money, relations) based on field definitions.
Conditionals
Use t-if, t-elif, and t-else to show content based on conditions.
- t-if — conditional inclusion if expression is true
- t-elif — else-if
- t-else — fallback
<t t-if="user.is_admin">
<p>Welcome, admin!</p>
</t>
<t t-else="">
<p>Welcome, user!</p>
</t>
Loops (Foreach)
To repeat content for each item in a list, use t-foreach with t-as.
<t t-foreach="orders" t-as="order">
<div>
<span t-esc="order.name"/>
<span t-esc="order.amount_total"/>
</div>
</t>
Inside loops, you can use t-if/t-esc etc. The loop introduces a scope for the variable (order above).
Setting Attributes and Variables
You often need to conditionally set HTML attributes or create temporary variables.
Dynamic Attributes
Use t-att to dynamically set one or more attributes in a QWeb template.
<span t-att-class="'badge ' + ('success' if status == 'done' else 'warning')">
<t t-esc="status"/>
</span>Temporary Variables
t-set — define a variable in template scope. Example:
<t t-set="full_name" t-value="user.first_name + ' ' + user.last_name"/>
<p t-esc="full_name"/>
Sub-Templates and Template Inheritance
Sub-Templates
Define reusable chunks of templates with <template> and call them using t-call.
<template id="my_template">
<p t-esc="message"/>
</template>
<template id="other">
<t t-call="my_template">
<t t-with="{'message': 'Hello from call'}"/>
</t>
</template>
Inheritance
Templates can inherit from other templates, replacing or extending parts. Odoo uses xpath expressions for identifying parts to be replaced/inserted. There is an old inheritance mechanism (deprecated) and the newer mechanism. You typically use something like:
<template inherit_id="base.template_to_inherit">
<xpath expr="//div[@class='header']" position="inside">
<p>New content in header</p>
</xpath>
</template>
Inheritance allows modules to customize existing templates without duplicating.
Dynamic QWeb Rendering
QWeb templates can also be rendered dynamically in Python or controllers.
from odoo import http
class MyController(http.Controller):
@http.route('/hello', auth='public')
def hello(self, **kw):
return http.request.render('my_module.hello_template', {
'user': http.request.env.user,
})
This enables fully dynamic page rendering or API responses using QWeb.
QWeb in Reports
Odoo uses QWeb as the report engine for PDF, HTML, and XLSX formats.
<template id="report_order_document">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<h2 t-esc="o.name"/>
<p>Total: <t t-esc="o.amount_total"/></p>
</t>
</t>
</template>
Report registration in XML:
<report
id="action_report_order"
model="sale.order"
string="Sales Order"
report_type="qweb-pdf"
name="my_module.report_order_document"
/>
QWeb in Emails
Odoo’s mail templates use QWeb for dynamic message rendering:
<t t-name="mail_template_order">
<p>Hello <t t-esc="object.partner_id.name"/>,</p>
<p>Your order <t t-esc="object.name"/> has been confirmed.</p>
</t>
Email templates can use variables such as:
- object > the current record
- user > the logged-in user
- ctx > context variables
This makes it easy to generate personalized messages automatically
QWeb in Website Pages
QWeb powers Odoo’s website and portal templates. It allows rendering dynamic content directly in pages.
<template id="website_sale_products" name="Products">
<t t-foreach="products" t-as="product">
<div class="product-card">
<h3 t-esc="product.name"/>
<span t-esc="product.list_price"/>
</div>
</t>
</template>
In Controller:
@http.route('/products', auth='public')
def products(self):
products = http.request.env['product.template'].search([])
return http.request.render('my_module.website_sale_products', {'products': products})Advanced Output: Python, Escaping, Deprecated Directives
- You can embed Python expressions in directives (e.g. inside t-if, t-value, etc). However, logic should be minimal in templates. Heavy business logic still belongs to models or controllers.
- Double-escaping / forcing escape: sometimes you need to ensure that content is escaped even if raw, or handle special characters safely. QWeb helps avoid XSS vulnerabilities.
- Deprecated directives: some old output directives are deprecated. For example, t-out is old; use t-esc or t-raw. The docs warn about certain deprecated behaviors.
Caching: t-cache, t-nocache, Scoped Caching
Rendering large templates or repeating parts can be expensive. Caching directives allow reusing rendered fragments.
- t-cache: cache the result of a template fragment based on input variables. If the same inputs occur again, the engine uses the cached rendering. Very useful for static or rarely changing data.
- t-nocache: the opposite; ensures part is always freshly rendered (not from cache). Use when dynamic content must always reflect the latest state.
Also, caching interacts with scope and variable binding: if you have loops or sub-templates inside cache, scoping rules matter. The docs cover “What if there is a t-cache inside a t-cache?”, “base of t-cache,” “t-nocache and scoped root values,” etc.
Helpers, Debugging, Best Practices
- Helpers: QWeb has helper functions for formatting fields (dates, monetary, relational fields), which know about field type metadata. Makes template output more consistent.
- Debugging: The docs describe how to inspect/enable debugging, how to view template inheritance chain, locate where a given snippet comes from, etc. Useful when templating issues arise.
- Best Practices:
- Keep logic in the Python/model side. Templates should be mainly for presentation.
- Use t-esc unless you absolutely have safe HTML content.
- Use template inheritance for customization rather than copying templates.
- Make use of caching for performance, but be careful: dynamic content needs no caching or proper invalidation.
- Use clear variable names, and wrap complex logic in helper methods in the model rather than inline expressions.
<template id="order_card">
<t t-set="is_high_value" t-value="order.amount_total > 1000"/>
<div t-attrs='{"class": "order-card " + (" high" if is_high_value else "")}'>
<h2 t-esc="order.name"/>
<p>Customer: <t t-esc="order.partner_id.name"/></p>
<p t-if="is_high_value">?? High value order!</p>
<div>
<t t-foreach="order.order_line" t-as="line">
<div>
<span t-esc="line.product_id.display_name"/> — qty: <t t-esc="line.qty"/>
</div>
</t>
</div>
</div>
</template>
QWeb in Odoo 19 is a powerful templating system, giving developers the tools to render dynamic content cleanly and efficiently. By using its directives smartly—output statements, loops, conditionals, attributes, sub-templates, inheritance, and caching—you can build UI, website pages, and report templates that are maintainable, performant, and secure.
To read more about What is Qweb Templates & Key Features of Qweb in Odoo 18, refer to our blog What is Qweb Templates & Key Features of Qweb in Odoo 18.