Skip to content

Odoo QWeb Reports & PDF Generation: Developer Guide

DeployMonkey Team · March 22, 2026 14 min read

How Odoo Reports Work

Odoo generates PDF reports using QWeb (its template engine) + wkhtmltopdf (HTML-to-PDF converter). You write an HTML template with QWeb directives, Odoo renders it with data, and wkhtmltopdf converts the HTML to PDF.

Creating a Custom Report

Step 1: Report Action

<!-- views/report_equipment.xml -->
<record id="action_report_equipment" model="ir.actions.report">
    <field name="name">Equipment Report</field>
    <field name="model">equipment.item</field>
    <field name="report_type">qweb-pdf</field>
    <field name="report_name">equipment_tracking.report_equipment</field>
    <field name="report_file">equipment_tracking.report_equipment</field>
    <field name="binding_model_id" ref="model_equipment_item"/>
    <field name="binding_type">report</field>
</record>

Step 2: QWeb Template

<template id="report_equipment">
    <t t-call="web.html_container">
        <t t-foreach="docs" t-as="doc">
            <t t-call="web.external_layout">
                <div class="page">
                    <h2>Equipment Report</h2>
                    <table class="table table-sm">
                        <thead>
                            <tr>
                                <th>Name</th>
                                <th>Serial Number</th>
                                <th>Department</th>
                                <th>Status</th>
                                <th>Purchase Cost</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr>
                                <td><t t-esc="doc.name"/></td>
                                <td><t t-esc="doc.serial_number"/></td>
                                <td><t t-esc="doc.department_id.name"/></td>
                                <td><t t-esc="doc.status"/></td>
                                <td><t t-esc="'%.2f' % doc.purchase_cost"/></td>
                            </tr>
                        </tbody>
                    </table>

                    <!-- Conditional content -->
                    <t t-if="doc.notes">
                        <h3>Notes</h3>
                        <p><t t-esc="doc.notes"/></p>
                    </t>

                    <!-- Warranty info -->
                    <div t-if="doc.warranty_expiry" class="mt-3">
                        <strong>Warranty expires:</strong>
                        <t t-esc="doc.warranty_expiry" t-options='{"widget": "date"}'/>
                    </div>
                </div>
            </t>
        </t>
    </t>
</template>

Key QWeb Directives

DirectivePurposeExample
t-escOutput escaped text<t t-esc="doc.name"/>
t-rawOutput unescaped HTML<t t-raw="doc.description"/>
t-ifConditional rendering<div t-if="doc.active">...</div>
t-foreachLoop<t t-foreach="doc.line_ids" t-as="line">
t-setSet variable<t t-set="total" t-value="0"/>
t-callInclude sub-template<t t-call="web.external_layout">
t-att-*Dynamic attribute<div t-att-class="'text-danger' if x else ''"/>
t-optionsFormatting optionst-options='{"widget": "monetary"}'

Formatting Values

<!-- Date formatting -->
<t t-esc="doc.date" t-options='{"widget": "date"}'/>

<!-- Monetary formatting -->
<t t-esc="doc.amount" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>

<!-- Float formatting -->
<t t-esc="'%.2f' % doc.quantity"/>

Page Breaks

<!-- Force page break between records -->
<div class="page" style="page-break-after: always;">
    <!-- Record content -->
</div>

Paper Format

<record id="paperformat_equipment" model="report.paperformat">
    <field name="name">Equipment Report Format</field>
    <field name="format">A4</field>
    <field name="margin_top">40</field>
    <field name="margin_bottom">20</field>
    <field name="margin_left">7</field>
    <field name="margin_right">7</field>
    <field name="header_spacing">35</field>
    <field name="orientation">Portrait</field>
    <field name="dpi">90</field>
</record>

Common Issues

  • Blank PDF → wkhtmltopdf not installed or wrong version
  • Missing CSS → wkhtmltopdf cannot reach Odoo static files (set report_url)
  • Report timeout → Large data set, increase limit_time_real
  • Wrong paper size → Specify paper format in report action

AI-Powered Report Creation

Claude Code generates QWeb report templates from natural language descriptions: "Create a PDF report for equipment showing all items grouped by department with subtotals." Deploy and test on DeployMonkey.