Customize Report Headers & Footers in Odoo 18
By Braincuber Team
Published on February 4, 2026
Every invoice, quotation, and delivery slip your company sends represents your brand. Yet many businesses run on Odoo's default report templates—generic headers with misaligned logos and footers missing critical contact information. At Atlas Manufacturing Co., their sales team was embarrassed sending quotes to enterprise clients with reports that looked like they came from a template factory.
This guide shows you how to customize report headers and footers across all Odoo 18 documents using QWeb template inheritance. You'll learn the proper way to override layouts without breaking core templates, ensuring your customizations survive Odoo upgrades while giving every report a professional, branded appearance.
What You'll Customize:
- Company logo placement and sizing
- Header layout with full contact details
- Professional footer with page numbers
- Consistent branding across all report types
Understanding Odoo Report Layouts
Odoo 18 uses the QWeb templating engine to generate PDF reports. All reports inherit from base layout templates that define the header and footer structure. Understanding this hierarchy is essential before making customizations.
Built-in Layout Options
| Layout Template | Description | Best For |
|---|---|---|
| external_layout_standard | Classic layout with company info in header | General business use |
| external_layout_striped | Alternating row colors in tables | Reports with many line items |
| external_layout_bold | Prominent headers with bold styling | Executive summaries |
| external_layout_boxed | Bordered sections for separation | Complex multi-section reports |
Tip: Choose your base layout in Settings → General Settings → Document Layout before customizing. Your inherited template will extend this selection.
Step 1: Enable Developer Mode
Template customization requires developer mode access. Enable it through settings:
- Navigate to
Settings→General Settings - Scroll to
Developer Toolssection - Click
Activate the developer mode - The URL will update with
?debug=1
Step 2: Create Your Custom Module
Report customizations should live in a dedicated module to keep them organized and upgrade-safe. Here's the proper structure:
# Module Structure
custom_report_layout/
├── __init__.py
├── __manifest__.py
└── views/
└── report_templates.xml
Module Manifest
# __manifest__.py
{
'name': 'Custom Report Layout',
'version': '18.0.1.0.0',
'category': 'Tools',
'summary': 'Branded headers and footers for all reports',
'description': '''
Customizes report headers and footers with company
branding, contact information, and professional styling.
''',
'author': 'Atlas Manufacturing Co.',
'depends': ['web'],
'data': [
'views/report_templates.xml',
],
'installable': True,
'auto_install': False,
'license': 'LGPL-3',
}
Step 3: Inherit the Base Layout Template
The key to safe customization is inheriting existing templates rather than replacing them entirely. This preserves upgrade compatibility and core functionality.
Template Inheritance Syntax
<!-- Basic inheritance structure -->
<template id="your_custom_template_id"
inherit_id="web.external_layout_standard">
<!-- XPath expressions to modify specific elements -->
</template>
XPath Position Attributes:
- replace: Completely replaces the matched element with your content
- before: Inserts your content immediately before the matched element
- after: Inserts your content immediately after the matched element
- inside: Inserts content inside the matched element (at the end)
- attributes: Modifies attributes of the matched element
Step 4: Customize the Report Header
The header appears at the top of every page. Let's create a professional header with logo, company name, and contact details arranged in a clean layout.
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- Custom Header Layout for Atlas Manufacturing -->
<template id="custom_header_footer_layout"
inherit_id="web.external_layout_standard">
<!-- Replace the Header Section -->
<xpath expr="//div[contains(@t-attf-class, 'header o_company_')]"
position="replace">
<div t-attf-class="header o_company_#{company.id}_layout"
style="border-bottom: 2px solid #2C3E50; padding-bottom: 15px;">
<!-- Two-Column Header Layout -->
<div class="row align-items-center">
<!-- Left Column: Company Logo -->
<div class="col-4">
<img t-if="company.logo"
t-att-src="image_data_uri(company.logo)"
style="max-height: 80px; max-width: 200px;"
alt="Company Logo"/>
</div>
<!-- Right Column: Company Details -->
<div class="col-8 text-end">
<!-- Company Name -->
<h2 t-if="company.name"
style="color: #2C3E50; font-weight: bold; margin: 0;">
<t t-esc="company.name"/>
</h2>
<!-- Address Line -->
<p style="margin: 5px 0; color: #555; font-size: 12px;">
<t t-if="company.street">
<t t-esc="company.street"/>
</t>
<t t-if="company.city">
, <t t-esc="company.city"/>
</t>
<t t-if="company.state_id">
, <t t-esc="company.state_id.name"/>
</t>
<t t-if="company.zip">
<t t-esc="company.zip"/>
</t>
</p>
<!-- Contact Information -->
<p style="margin: 0; color: #555; font-size: 11px;">
<t t-if="company.phone">
<strong>Tel:</strong> <t t-esc="company.phone"/>
</t>
<t t-if="company.email">
| <strong>Email:</strong> <t t-esc="company.email"/>
</t>
<t t-if="company.website">
| <strong>Web:</strong> <t t-esc="company.website"/>
</t>
</p>
<!-- Tax ID (if applicable) -->
<p t-if="company.vat"
style="margin: 3px 0 0 0; color: #777; font-size: 10px;">
<strong>Tax ID:</strong> <t t-esc="company.vat"/>
</p>
</div>
</div>
</div>
</xpath>
</template>
</odoo>
Step 5: Customize the Report Footer
The footer appears at the bottom of every page, typically containing page numbers, company information, and legal notices.
<!-- Add this inside the same template, after the header xpath -->
<!-- Replace the Footer Section -->
<xpath expr="//div[contains(@class, 'o_footer')]" position="replace">
<div class="footer o_footer"
style="border-top: 1px solid #ddd; padding-top: 10px; font-size: 10px;">
<div class="row">
<!-- Left: Company Info -->
<div class="col-6 text-start">
<strong><t t-esc="company.name"/></strong>
<br/>
<span style="color: #666;">
<t t-if="company.phone">
<t t-esc="company.phone"/>
</t>
<t t-if="company.email">
| <t t-esc="company.email"/>
</t>
</span>
</div>
<!-- Right: Page Numbers -->
<div class="col-6 text-end">
<span style="color: #666;">
Page <span class="page"/> of <span class="topage"/>
</span>
</div>
</div>
<!-- Bottom: Legal Notice -->
<div class="row mt-1">
<div class="col-12 text-center">
<span style="color: #999; font-size: 9px;">
This document is computer generated and valid without signature.
</span>
</div>
</div>
</div>
</xpath>
Note: The <span class="page"/> and <span class="topage"/> classes are special PDF rendering placeholders that wkhtmltopdf replaces with actual page numbers.
Step 6: Complete Template File
Here's the complete XML file combining both header and footer customizations:
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="atlas_custom_report_layout"
inherit_id="web.external_layout_standard">
<!-- ============== CUSTOM HEADER ============== -->
<xpath expr="//div[contains(@t-attf-class, 'header o_company_')]"
position="replace">
<div t-attf-class="header o_company_#{company.id}_layout"
style="border-bottom: 2px solid #2C3E50;
padding-bottom: 15px;
margin-bottom: 20px;">
<div class="row align-items-center">
<div class="col-4">
<img t-if="company.logo"
t-att-src="image_data_uri(company.logo)"
style="max-height: 80px; max-width: 200px;"
alt="Logo"/>
</div>
<div class="col-8 text-end">
<h2 t-if="company.name"
style="color: #2C3E50; font-weight: bold; margin: 0;">
<t t-esc="company.name"/>
</h2>
<p style="margin: 5px 0; color: #555; font-size: 12px;">
<t t-esc="company.street"/>
<t t-if="company.city">, <t t-esc="company.city"/></t>
<t t-if="company.zip"> <t t-esc="company.zip"/></t>
</p>
<p style="margin: 0; color: #555; font-size: 11px;">
<t t-if="company.phone">Tel: <t t-esc="company.phone"/></t>
<t t-if="company.email"> | <t t-esc="company.email"/></t>
<t t-if="company.website"> | <t t-esc="company.website"/></t>
</p>
</div>
</div>
</div>
</xpath>
<!-- ============== CUSTOM FOOTER ============== -->
<xpath expr="//div[contains(@class, 'o_footer')]" position="replace">
<div class="footer o_footer"
style="border-top: 1px solid #ddd;
padding-top: 10px;
font-size: 10px;">
<div class="row">
<div class="col-6 text-start">
<strong><t t-esc="company.name"/></strong><br/>
<span style="color: #666;">
<t t-esc="company.phone"/> | <t t-esc="company.email"/>
</span>
</div>
<div class="col-6 text-end">
<span style="color: #666;">
Page <span class="page"/> of <span class="topage"/>
</span>
</div>
</div>
</div>
</xpath>
</template>
</odoo>
Step 7: Install and Test
- Install Module: Go to
Apps, remove the "Apps" filter, search for your module name, and clickInstall - Generate Test Report: Navigate to any sales order, invoice, or purchase order and click
Print - Verify Layout: Check that your custom header and footer appear correctly
- Test Multi-Page: Create a report with enough content to span multiple pages and verify page numbers work
Troubleshooting Tips:
- Template Not Applied: Clear browser cache and restart Odoo service
- XPath Not Matching: Double-check the exact class names in the original template
- Logo Not Showing: Verify company logo is uploaded in
Settings → Companies - Styles Not Rendering: Use inline styles in reports as external CSS may not load in PDF
Accessing Document Data in Templates
Beyond company information, you can access document-specific data using QWeb variables:
<!-- Access the current document being printed -->
<t t-esc="o.name"/> <!-- Document reference (SO001, INV/2024/0001) -->
<t t-esc="o.partner_id.name"/> <!-- Customer/Vendor name -->
<t t-esc="o.date_order"/> <!-- Order/Invoice date -->
<t t-esc="o.amount_total"/> <!-- Total amount -->
<!-- Format currency values -->
<span t-field="o.amount_total"
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
<!-- Format dates -->
<span t-field="o.date_order" t-options='{"format": "dd/MM/yyyy"}'/>
<!-- Conditional content -->
<t t-if="o.state == 'sale'">
<span class="badge bg-success">Confirmed</span>
</t>
Frequently Asked Questions
<img src="/module_name/static/src/img/banner.png"/>. For dynamic images from database fields, use: <img t-att-src="image_data_uri(record.image_field)"/>.
company variable, which is set based on the document being printed. Each company can have its own logo, colors, and contact details in Settings → Companies. For company-specific styling, use conditions like: <t t-if="company.id == 1">...</t> or reference company-specific fields.
Conclusion
Professional report layouts make a significant impression on customers, vendors, and partners. By using Odoo 18's template inheritance system, you can create branded headers and footers that apply consistently across all your business documents—invoices, quotations, delivery slips, and more—without modifying core code.
The approach shown here—creating a dedicated module with inherited templates and XPath modifications—ensures your customizations are upgrade-safe, maintainable, and can be easily transferred between Odoo instances. Start with the standard external layout, customize the header and footer to match your brand, and test across different report types to ensure consistency.
Need Custom Report Development?
Our Odoo experts can design and implement custom report layouts that match your brand guidelines, including complex multi-page reports, conditional formatting, and integration with external document management systems.
