تخطي للذهاب إلى المحتوى

Odoo QWeb Report Customization: Developer Guide to PDF Reports, Invoices, and Templates

16 أبريل 2026 بواسطة
Odoo QWeb Report Customization: Developer Guide to PDF Reports, Invoices, and Templates
Odoo Skillz, Odoo Skillz
لا توجد تعليقات بعد

TL;DR: What You Need to Know

80%
of businesses customize invoices
15min
average time per report template
5
key report types to customize
  • XML-based templating: QWeb uses Odoo's XML report engine with XPath expressions
  • Inherit, don't override: Always use report inheritance to preserve upgrade compatibility
  • CSS + Bootstrap: Style reports with Bootstrap 5 classes and custom CSS variables

Odoo's QWeb reporting engine is the backbone of every PDF document your business generates. Invoices, delivery slips, purchase orders, quotes, and internal reports all flow through QWeb templates. Yet most businesses accept the default layouts without realizing how much customization is possible without touching core code.

Over 80% of Odoo implementations require at least minor report modifications to match company branding, add custom fields, or comply with local regulations. Understanding QWeb customization gives you full control over every document that leaves your system.

  • Brand consistency: Match every report to your corporate identity
  • Regulatory compliance: Add required fields for tax authorities
  • Operational efficiency: Surface critical data that warehouse and accounting teams need

Understanding the QWeb Report Architecture

QWeb is Odoo's XML-based templating engine. Every PDF report follows a three-layer architecture: the report action, the template definition, and the rendering pipeline. Understanding this structure is essential before making any modifications.

QWeb report architecture diagram showing template inheritance layers

1. Report Action Definition

The report action in ir.actions.report links a model to a QWeb template and specifies the output format:

<record id="action_report_invoice" model="ir.actions.report">
    <field name="name">Invoices</field>
    <field name="model">account.move</field>
    <field name="report_type">qweb-pdf</field>
    <field name="report_name">account.report_invoice_document</field>
    <field name="report_file">account.report_invoice_document</field>
    <field name="print_report_name">
        'Invoice - %s' % (object.name or 'Draft')
    </field>
</record>

The report_name points to the template that generates the actual content. The print_report_name field controls the generated filename. Changing the filename pattern helps users identify documents in download folders.

2. Template Structure

Each QWeb template is an XML record with a specific structure. The template receives the document object (e.g., an invoice record) and renders it as HTML, which is then converted to PDF by wkhtmltopdf:

<template id="report_invoice_document">
    <t t-call="web.external_layout">
        <t t-set="doc" t-value="doc"/>
        <div class="page">
            <!-- Report content here -->
        </div>
    </t>
</template>

The web.external_layout wrapper provides the standard header and footer. You can swap this for web.internal_layout for reports that should not include company branding.

3. The Rendering Pipeline

When a user clicks "Print," Odoo processes the template through these steps:

  • Template resolution: Finds the correct template based on report action
  • Data binding: Attaches the record set to the template context
  • HTML generation: Processes QWeb directives (t-if, t-foreach, t-field)
  • PDF conversion: wkhtmltopdf converts the HTML output to a PDF file

Understanding this pipeline helps you debug rendering issues. If your data appears correctly in the HTML preview but not in the PDF, the issue is likely in CSS compatibility with wkhtmltopdf.

  • Use the preview: Debug templates in HTML mode before generating PDFs
  • Check wkhtmltopdf version: Odoo 17+ requires 0.12.6 or newer
  • Test with real data: Template behavior changes with empty vs populated fields

Inheriting Reports: The Safe Customization Pattern

Never modify core Odoo templates directly. Direct modifications get overwritten during module updates and break upgrade paths. Instead, use Odoo's inheritance mechanism to layer your changes on top of the standard templates.

XML inheritance pattern showing base template and custom overlay

4. XPath-Based Inheritance

XPath inheritance lets you target specific elements within a template and modify them. The most common operations are replacing, inserting before, inserting after, and setting attributes:

<template id="report_invoice_inherit" 
    inherit_id="account.report_invoice_document">
    <!-- Replace the customer address block -->
    <xpath expr="//div[@name='partner_header']" position="replace">
        <div class="col-6" name="custom_partner_header">
            <span t-field="o.partner_id"
                t-options='{"widget": "contact", 
                           "fields": ["name", "phone", "email"],
                           "no_marker": true}'/>
            <p t-if="o.partner_id.vat">
                <strong>VAT:</strong> 
                <span t-field="o.partner_id.vat"/>
            </p>
        </div>
    </xpath>
</template>

The expr attribute is an XPath expression that locates the target element. The position attribute specifies the operation. Common positions include replace, before, after, inside, and attributes.

5. Attribute Modification

To change an element's attributes without replacing its content, use the attributes position:

<xpath expr="//table[@name='invoice_line_table']/thead/tr/th[1]" 
    position="attributes">
    <attribute name="class">text-center fw-bold</attribute>
</xpath>

This approach is ideal for adding CSS classes, changing data attributes, or modifying visibility classes. It preserves the element's content and child structure while updating its properties.

6. Creating Entirely New Reports

When you need a completely new report (not a modification of an existing one), create a new template and register it with a new report action:

<template id="report_custom_packing_slip">
    <t t-call="web.external_layout">
        <div class="page">
            <h2>Packing Slip: 
                <span t-field="o.name"/>
            </h2>
            <table class="table table-sm">
                <thead>
                    <tr>
                        <th>Product</th>
                        <th>Quantity</th>
                        <th>Location</th>
                    </tr>
                </thead>
                <t t-foreach="o.move_line_ids" t-as="line">
                    <tr>
                        <td><span t-field="line.product_id.name"/></td>
                        <td><span t-field="line.qty_done"/></td>
                        <td><span t-field="line.location_dest_id.name"/></td>
                    </tr>
                </t>
            </table>
        </div>
    </t>
</template>
  • Unique template IDs: Prefix with your module name to avoid collisions
  • Use t-field: Always use t-field instead of raw field access for proper formatting
  • Test XPath expressions: Use browser dev tools to verify XPath before deploying

Customizing Invoice Templates: Practical Examples

Invoice customization is the most common QWeb modification. Businesses need to add payment terms, bank details, custom line columns, terms and conditions, and regulatory fields. Here are the most requested invoice customizations.

Customized Odoo invoice template with branded header and custom fields

7. Adding Custom Columns to Invoice Lines

Standard Odoo invoices show product, description, quantity, unit price, and taxes. Many businesses need additional columns like project codes, batch numbers, or internal references:

<!-- Add a 'Project Code' column after Description -->
<xpath expr="//table[@name='invoice_line_table']/thead/tr" 
    position="inside">
    <th class="text-start">Project Code</th>
</xpath>

<xpath expr="//table[@name='invoice_line_table']/tbody//tr" 
    position="inside">
    <td>
        <span t-field="line.analytic_distribution" 
              t-options='{"widget": "analytic_distribution"}'/>
    </td>
</xpath>

8. Adding Payment Information and QR Codes

European businesses need SEPA QR codes on invoices. Here's how to add a payment information block:

<xpath expr="//div[@name='payment_term']" position="after">
    <div class="row mt-4" name="payment_info">
        <div class="col-6">
            <h4>Payment Information</h4>
            <p><strong>Bank:</strong> 
               <span t-field="o.journal_id.bank_id.name"/></p>
            <p><strong>IBAN:</strong> 
               <span t-field="o.journal_id.bank_id.sanitized_acc_number"/></p>
            <p><strong>BIC:</strong> 
               <span t-field="o.journal_id.bank_id.bank_bic"/></p>
        </div>
    </div>
</xpath>

9. Conditional Content Based on Document State

Show different content depending on the invoice state, customer type, or currency:

<t t-if="o.currency_id.name == 'USD'">
    <p>All amounts are in US Dollars.</p>
</t>
<t t-if="o.partner_id.country_id.code == 'DE'">
    <p>Steuernummer: <span t-field="o.company_id.vat"/></p>
</t>
<t t-if="o.payment_state == 'not_paid'">
    <p class="text-danger fw-bold">PAYMENT DUE IMMEDIATELY</p>
</t>
  • Condition before field: Wrap optional fields in t-if to hide empty values
  • Currency formatting: Use t-field-options with {"widget": "monetary"}
  • Multi-language: Use t-translation="off" for content that should not be translated

Advanced QWeb Techniques

Once you understand the basics, QWeb offers powerful features for complex report requirements. These techniques handle multi-page layouts, dynamic styling, sub-report embedding, and Python-assisted data processing.

Advanced QWeb report with multi-page layout and conditional formatting

10. Multi-Page Reports with Page Breaks

Control where pages break in PDF output using CSS page-break properties:

<div style="page-break-after: always;">
    <h2>Section 1: Order Summary</h2>
    <!-- Content -->
</div>
<div style="page-break-before: always;">
    <h2>Section 2: Line Details</h2>
    <!-- Content -->
</div>

You can also use page-break-inside: avoid on tables to prevent rows from splitting across pages. This is essential for financial reports where a single line item must stay on one page.

11. Dynamic Styling with Inline Expressions

Apply conditional styling based on data values. This is particularly useful for highlighting overdue invoices, low stock warnings, or budget overruns:

<tr t-attf-class="#{
    'table-danger' if o.payment_state == 'overdue' 
    else 'table-success' if o.payment_state == 'paid'
    else ''
}">
    <td><span t-field="o.name"/></td>
    <td><span t-field="o.amount_total"/></td>
</tr>

12. Calling Python Methods from Templates

You can call methods defined on the report model directly from QWeb templates:

<!-- In Python report model -->
class CustomReport(models.AbstractModel):
    _name = 'report.custom.custom_report'
    
    def _get_custom_data(self, doc):
        return self.env['custom.model'].search([
            ('order_id', '=', doc.id)
        ])

<!-- In QWeb template -->
<t t-set="custom_data" 
   t-value="docs[0]._get_custom_data()"/>
<t t-foreach="custom_data" t-as="item">
    <p><span t-field="item.name"/></p>
</t>

This pattern lets you perform complex data aggregation in Python and pass the results to the template for rendering. It keeps the template clean and moves business logic to Python code where it belongs.

  • Page numbers: Use <span class="page"/> and <span class="topage"/> for automatic page numbering
  • Custom fonts: Define @font-face in the <style> block at the top of your template
  • Barcode generation: Use <img t-att-src="'/report/barcode/?type=%s&value=%s' % ('Code128', o.name)"/>

Testing and Debugging QWeb Reports

Proper testing prevents broken reports from reaching production. Odoo provides several debugging tools and testing strategies for QWeb report development.

13. HTML Preview Mode

Before generating a PDF, preview your report as HTML to debug layout issues faster. Navigate to the record and select "Print" then choose "Preview in HTML" from the dropdown. The HTML preview renders instantly without wkhtmltopdf conversion, making it much faster for iterative development.

14. Debugging Template Errors

Common template errors and their solutions:

  • "XPath element not found": The XPath expression didn't match any element. Check the template structure in Developer Mode and verify the element still exists in your Odoo version
  • "t-field on non-record": You're trying to use t-field on a value that isn't a record. Use t-esc for plain values instead
  • "wkhtmltopdf failed": Usually a CSS compatibility issue. Remove complex CSS selectors, gradients, or flexbox layouts that wkhtmltopdf doesn't support
  • "QWebException: 'NoneType' has no attribute": A field is empty. Wrap access in t-if to check for existence before rendering

15. Version Control for Report Templates

Store report customizations in a dedicated module rather than editing through the Odoo interface. This enables version control, code review, and safe deployment across environments:

custom_reports/
├── __manifest__.py
├── views/
│   └── report_templates.xml
├── reports/
│   ├── __init__.py
│   └── custom_reports.py
└── static/
    └── description/
        └── icon.png
  • Module-based approach: Keeps customizations deployable and versionable
  • Upgrade testing: Test templates against new Odoo versions before upgrading production
  • Backup templates: Export customized templates via Odoo's export feature before major changes

QWeb report customization in Odoo follows a clear inheritance pattern that protects upgrade compatibility. Always inherit rather than override core templates, use t-field for proper data formatting, test in HTML preview mode before PDF generation, and store customizations in dedicated modules for version control and safe deployment.

Frequently Asked Questions

Can I customize Odoo reports without coding?

Basic modifications like adding your company logo, changing colors, and adjusting header/footer text can be done through Odoo's Document Layout settings in General Settings. However, adding custom fields, changing table layouts, or creating entirely new reports requires QWeb XML customization in a custom module.

How do I find the XPath expression for an element in an Odoo report?

Activate Developer Mode, go to Settings > Technical > User Interface > Views, and search for the report template name. Open the template in XML view to see the full structure. You can also use the browser's Inspect Element on the HTML preview to identify element names and classes, then construct the XPath expression using those attributes.

Will my report customizations survive an Odoo version upgrade?

Inherited customizations in a separate module generally survive upgrades because they layer on top of the base templates. However, if Odoo changes the structure of the base template (renaming elements, removing classes), your XPath expressions may break. Test all custom reports against the new version before upgrading production.

How do I add a barcode or QR code to an Odoo report?

Use Odoo's built-in barcode report endpoint: <img t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('Code128', o.name, 200, 60)"/>. For QR codes, change the type parameter to 'QR'. The barcode endpoint supports Code128, EAN13, QR, and Code39 formats.

Why does my QWeb report look different in PDF vs HTML preview?

The PDF is generated by wkhtmltopdf, which uses an older WebKit rendering engine. It doesn't fully support modern CSS features like flexbox, CSS grid, or certain positioning properties. If your layout looks correct in HTML but broken in PDF, simplify the CSS by replacing flexbox with float-based layouts, avoid CSS grid, and use inline styles instead of external stylesheets.

Need Custom Reports for Your Odoo System?

Explore Odoo Skillz for pre-built report templates, customization modules, and expert guidance to get your documents looking exactly right.

Browse Modules Contact Us

References

  1. Odoo Documentation — Reports & QWeb Templates (2026)
  2. Odoo Community Association — Reporting Engine Modules (2026)
  3. wkhtmltopdf — HTML to PDF Converter Documentation (2026)
  4. Odoo Mates — Custom Report Development Guide (2025)
  5. Cybrosys — Odoo Report Customization Best Practices (2025)
شارك هذا المنشور
علامات التصنيف
تسجيل الدخول حتى تترك تعليقاً