In Odoo 19, the website module powers dynamic, editable web pages using QWeb templates, controllers, and views. Modifying existing pages—such as the homepage, product pages, or shop—without altering core code is a best practice. This ensures upgrade compatibility and maintainability. Odoo's inheritance system lets you extend templates and views safely.
This guide walks you through creating a custom module to programmatically modify web pages. We'll use a practical example: adding a "Top Selling Products" section to the homepage (website.homepage template). This involves:
- Extending models for data.
- Customizing controllers for logic.
- Inheriting templates for UI changes.
By the end, you'll have a reusable module. All code is based on Odoo 19's framework, with inheritance via XPath for precise modifications.
Step 1: Set Up the Custom Module Structure
Create the following files in custom_website_mods/:
custom_website_mods/
+-- __init__.py
+-- __manifest__.py
+-- models/
¦ +-- __init__.py
¦ +-- product_template.py
+-- controllers/
¦ +-- __init__.py
¦ +-- main.py
+-- views/
+-- homepage_templates.xml
- __init__.py (root): from . import models, controllers
- models/__init__.py: from . import product_template
- controllers/__init__.py: from . import main
Module Manifest (__manifest__.py)
Define dependencies and load files.
{
'name': 'Custom Website Modifications',
'version': '19.0.1.0.0',
'category': 'Website',
'summary': 'Modify existing web pages through code',
'depends': ['website', 'website_sale'], # Core website and eCommerce
'data': [
'views/homepage_templates.xml',
],
'installable': True,
'auto_install': False,
}Install the module via Apps > Update Apps List > Search "Custom Website Modifications" > Install.
Step 2: Extend Models for Custom Data
To display "top-selling" products, add a boolean field to product.template for manual tagging (or compute dynamically from sales orders).
In models/product_template.py:
from odoo import fields, models
class ProductTemplate(models.Model):
_inherit = 'product.template'
is_top_selling = fields.Boolean(
string='Top Selling',
help='Mark this product as top-selling for homepage display.'
)
This inherits the existing product.template model without modifying the original. Update the module to apply the field (it'll appear in product forms).
Step 3: Customize Controllers for Logic
Controllers handle routing and data preparation. Inherit website to extend the homepage route.
In controllers/main.py:
import odoo.http as http
from odoo.http import request
from odoo.addons.website.controllers.main import Website
class WebsiteTopSelling(Website):
@http.route('/', type='http', auth='public', website=True)
def index(self, **kw):
# Call parent to get default homepage data
response = super(WebsiteTopSelling, self).index(**kw)
# Fetch top-selling products (limit 4, published only)
top_products = request.env['product.template'].search([
('is_top_selling', '=', True),
('website_published', '=', True),
], limit=4, order='sales_count desc')
# Pass to template
response.qcontext['top_selling_products'] = top_products
return response
This overrides the root route (/) to inject custom data into the QContext, making top_selling_products available in templates. Restart Odoo and update the module.
Step 4: Inherit Templates for UI Modifications
Use QWeb inheritance to modify the website.homepage template. Add a new section inside the main wrapper.
In views/homepage_templates.xml:
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="homepage_top_selling" inherit_id="website.homepage" name="Top Selling Products Section">
<xpath expr="//div[@id='wrap']" position="inside">
<div class="container mt-5 mb-5">
<h2 class="text-center mb-4">Top Selling Products</h2>
<div class="row">
<t t-foreach="top_selling_products" t-as="product">
<div class="col-md-3 mb-4">
<div class="card h-100">
<img t-att-src="product.image_1920 or '/web/static/src/img/placeholder.png'"
class="card-img-top" alt="Product Image" style="height: 200px; object-fit: cover;"/>
<div class="card-body d-flex flex-column">
<h5 class="card-title"><t t-esc="product.name"/></h5>
<p class="card-text flex-grow-1"><t t-esc="product.list_price"/> €</p>
<a t-att-href="'/shop/product/%s' % product.id" class="btn btn-primary mt-auto">View Product</a>
</div>
</div>
</div>
</t>
</div>
</div>
</xpath>
</template>
</odoo>
- inherit_id="website.homepage": Targets the existing homepage template.
- <xpath>: Specifies where to insert (inside #wrap div).
- QWeb directives like t-foreach loop over products; t-esc escapes output for safety.
Update the module. Visit your homepage (/)—the section appears if products are marked as top-selling.
Step 5: Advanced Modifications
Modifying Menus
To rename/hide menus (e.g., change "Shop" to "Products"), inherit website.menu records.
Example in a new XML file (views/menu_mods.xml):
<odoo>
<record id="shop_menu_inherit" model="website.menu">
<field name="inherit_id" ref="website_sale.menu_shop_main"/>
<field name="name">Products</field>
</record>
<!-- Hide Blog Menu -->
<record id="blog_menu_hide" model="website.menu">
<field name="inherit_id" ref="website_blog.menu_blog_link"/>
<field name="is_visible" eval="False"/>
</record>
</odoo>
Add to __manifest__.py data list. This updates menu records without code hacks.
Custom Pages or Routes
For new pages, create templates and routes (see Odoo docs for full how-to)
<template id="custom_page" name="Custom Page" page="True">
<t t-call="website.layout">
<div id="wrap" class="container">
<h1>My Custom Page</h1>
<!-- Content -->
</div>
</t>
</template>
Route in controller: @http.route('/custom', type='http', auth='public', website=True).
Styling with CSS/JS
Add assets to __manifest__.py:
'assets': {
'web.assets_frontend': [
'custom_website_mods/static/src/scss/custom.scss',
'custom_website_mods/static/src/js/custom.js',
],
},Inherit layouts for global changes, e.g., .
Modifying existing web pages in Odoo 19 through code is both powerful and safe when done using Odoo’s inheritance mechanisms—model inheritance, controller overrides, and QWeb template inheritance with XPath. By creating a custom module, you can extend the homepage, product pages, menus, or any website component without touching core files, ensuring upgrade compatibility and clean maintainability.
To read more about How to Modify Existing Web Pages in Odoo 18, refer to our blog How to Modify Existing Web Pages in Odoo 18.