How to Add a Filter Option in Odoo 18 Website Portal
By Braincuber Team
Published on February 5, 2026
The Odoo Customer Portal is a powerful self-service tool, but as your data grows, finding specific records can become a challenge for your users. A flat list of 500 records helps no one.
In this tutorial, we will implement a dynamic Filter System for a custom "Warranty Claims" portal section. Using TechFix Solutions as our example, we'll enable customers to filter their claims by status: "In Progress," "Approved," or "Rejected," making the portal experience smoother and more professional.
Implementation Roadmap:
- Menu: Add a "Warranty Claims" entry to the portal home.
- Controller: Define the `searchbar_filters` logic in Python.
- Template: Render the filter dropdown using Odoo's `portal_searchbar`.
Step 1: Adding the Portal Menu Entry
First, we create the entry point. We modify the portal home to display the "Warranty Claims" tile and update the breadcrumbs navigation.
<odoo>
<!-- 1. Add to Breadcrumbs -->
<template id="portal_my_home_menu_warranty" inherit_id="portal.portal_breadcrumbs" priority="30">
<xpath expr="//ol[hasclass('o_portal_submenu')]" position="inside">
<li t-if="page_name == 'warranty'" t-attf-class="breadcrumb-item #{'active ' if warranty else ''}">
<a t-if="warranty" t-attf-href="/my/warranties?{{ keep_query() }}">Warranty Claims</a>
<t t-else="">Warranty Claims</t>
</li>
</xpath>
</template>
<!-- 2. Add to Portal Home Dashboard -->
<template id="portal_my_home_warranty" inherit_id="portal.portal_my_home" priority="30">
<div id="portal_client_category" position="inside">
<t t-call="portal.portal_docs_entry">
<t t-set="icon" t-value="'/techfix_warranty/static/src/img/warranty.svg'"/>
<t t-set="title">Warranty Claims</t>
<t t-set="url" t-value="'/my/warranties'"/>
<t t-set="text">Track your repair status</t>
<t t-set="placeholder_count" t-value="'warranty_count'"/>
</t>
</div>
</template>
</odoo>
Step 2: Controller Logic with Filters
This is where the magic happens. We define a searchbar_filters dictionary. Each key represents a filter option containing a label and a domain.
from odoo import http, _
from odoo.http import request
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
from collections import OrderedDict
class TechFixWarrantyPortal(CustomerPortal):
# 1. Update the Portal Counter
def _prepare_home_portal_values(self, counters):
values = super()._prepare_home_portal_values(counters)
if 'warranty_count' in counters:
values['warranty_count'] = request.env['techfix.warranty'].search_count([])
return values
# 2. Main List Controller
@http.route(['/my/warranties', '/my/warranties/page/<int:page>'], type='http', auth="user", website=True)
def portal_my_warranties(self, page=1, filterby=None, **kw):
Warranty = request.env['techfix.warranty']
# Base Domain: User sees their own claims
domain = [('partner_id', '=', request.env.user.partner_id.id)]
# Define Filter Options
searchbar_filters = {
'all': {'label': 'All Claims', 'domain': []},
'in_progress': {'label': 'In Progress', 'domain': [('state', 'in', ['draft', 'diagnosis'])]},
'approved': {'label': 'Approved', 'domain': [('state', '=', 'approved')]},
'completed': {'label': 'Completed', 'domain': [('state', '=', 'done')]},
'rejected': {'label': 'Rejected', 'domain': [('state', '=', 'cancel')]},
}
# Apply default filter if none selected
if not filterby:
filterby = 'all'
# Merge domains
domain += searchbar_filters[filterby]['domain']
# Pager Logic
claim_count = Warranty.search_count(domain)
pager = portal_pager(
url="/my/warranties",
url_args={'filterby': filterby},
total=claim_count,
page=page,
step=10
)
# Retrieve Records
claims = Warranty.search(domain, limit=10, offset=pager['offset'])
return request.render("techfix_warranty.portal_my_warranties_list", {
'warranties': claims,
'page_name': 'warranty',
'pager': pager,
'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
'filterby': filterby,
'default_url': '/my/warranties',
})
Step 3: Rendering the Filter Component
Finally, we create the list view. By enabling breadcrumbs_searchbar and calling portal.portal_searchbar, Odoo automatically renders the dropdown using the data we passed from the controller.
<odoo>
<template id="portal_my_warranties_list" name="My Warranties">
<t t-call="portal.portal_layout">
<t t-set="breadcrumbs_searchbar" t-value="True"/>
<t t-call="portal.portal_searchbar">
<t t-set="title">Warranty Claims</t>
</t>
<t t-call="portal.portal_table">
<thead>
<tr class="active">
<th>Reference</th>
<th>Date</th>
<th class="text-end">Status</th>
</tr>
</thead>
<tbody>
<t t-foreach="warranties" t-as="claim">
<tr>
<td>
<a t-attf-href="/my/warranties/#{claim.id}">
<span t-field="claim.name"/>
</a>
</td>
<td><span t-field="claim.date_received"/></td>
<td class="text-end">
<span t-field="claim.state"
t-att-class="'badge rounded-pill text-bg-' + ('success' if claim.state == 'done' else 'warning' if claim.state == 'in_progress' else 'secondary')"/>
</td>
</tr>
</t>
</tbody>
</t>
</t>
</template>
</odoo>
Conclusion
Adding filters to the Odoo Portal is a high-value customization that significantly improves user experience. By structuring your controller to accept a filterby argument and defining a clear domain dictionary, you give your customers the power to navigate their data effortlessly.
Upgrade Your Odoo Portal
Want to build advanced portal features like sorting, grouping, or chart visualizations? Our Odoo web development team can build the perfect customer experience for you.
