How to Add a Filter Option in Odoo 18 Website Portal
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.
Talk to a practice lead
Need this shipped for your business?
We have shipped 50+ Odoo deployments in the US, India, and EU since 2024. Bring your roadmap and your current system; we come back with a written fixed-price brief in 48 hours.
About the author
Founder & CEO, Braincuber Technologies
Founder and CEO of Braincuber. Has scoped and shipped 500+ Odoo, AI, and cloud projects for US mid-market and global brands. Takes every founder call personally — no SDR layer between buyers and the people building the system.
