Search Views in Odoo
Every list, kanban, pivot, and graph view in Odoo uses a search view to filter and group records. Search views define the search bar behavior: which fields are searchable, what predefined filters are available, and how records can be grouped. Mastering search views is essential for building usable Odoo interfaces.
Basic Search View Structure
<record id="view_maintenance_search" model="ir.ui.view">
<field name="name">maintenance.request.search</field>
<field name="model">maintenance.request</field>
<field name="arch" type="xml">
<search string="Search Requests">
<!-- Searchable fields -->
<field name="name" string="Reference"/>
<field name="description"/>
<field name="user_id"/>
<field name="partner_id"
filter_domain="['|',
('partner_id.name', 'ilike', self),
('partner_id.email', 'ilike', self)]"/>
<!-- Predefined filters -->
<separator/>
<filter name="filter_draft" string="Draft"
domain="[('state', '=', 'draft')]"/>
<filter name="filter_in_progress" string="In Progress"
domain="[('state', '=', 'in_progress')]"/>
<filter name="filter_done" string="Done"
domain="[('state', '=', 'done')]"/>
<separator/>
<filter name="filter_my" string="My Requests"
domain="[('user_id', '=', uid)]"/>
<filter name="filter_urgent" string="Urgent"
domain="[('priority', '=', '3')]"/>
<!-- Date filters -->
<separator/>
<filter name="filter_this_month" string="This Month"
date="create_date" default_period="this_month"/>
<filter name="filter_this_quarter" string="This Quarter"
date="create_date" default_period="this_quarter"/>
<!-- Group by -->
<group expand="0" string="Group By">
<filter name="group_state" string="Status"
context="{'group_by': 'state'}"/>
<filter name="group_user" string="Assigned To"
context="{'group_by': 'user_id'}"/>
<filter name="group_month" string="Month"
context="{'group_by': 'create_date:month'}"/>
</group>
</search>
</field>
</record>
Searchable Fields
The <field> elements in a search view define which fields appear in the search bar autocomplete. When users type, Odoo suggests matching fields:
<!-- Simple text search -->
<field name="name"/>
<!-- Search across related record fields -->
<field name="partner_id"
filter_domain="['|',
('partner_id.name', 'ilike', self),
('partner_id.email', 'ilike', self)]"/>
<!-- Search with operator -->
<field name="amount" filter_domain="[('amount', '>=', self)]"/>
The filter_domain attribute customizes the search behavior. The self variable holds the user's search input. Without filter_domain, Odoo uses the field's default search operator (ilike for Char, = for Many2one).
Predefined Filters
Filters add predefined domain conditions. Filters within the same separator group are OR-ed together. Filters across separator groups are AND-ed:
<!-- These two are OR-ed (same group) -->
<filter name="filter_draft" string="Draft"
domain="[('state', '=', 'draft')]"/>
<filter name="filter_submitted" string="Submitted"
domain="[('state', '=', 'submitted')]"/>
<separator/>
<!-- This is AND-ed with the group above -->
<filter name="filter_my" string="Mine"
domain="[('user_id', '=', uid)]"/>
So selecting Draft + Submitted + Mine yields: (state = draft OR state = submitted) AND user_id = current_user.
Date Filters
Date filters get special treatment with period selectors:
<filter name="filter_date" string="Creation Date"
date="create_date"/>
This creates a dropdown with options: Today, This Week, This Month, This Quarter, This Year, and custom date ranges. The default_period attribute pre-selects a period.
Group By Options
Group by filters use the context to pass grouping information:
<filter name="group_state" string="Status"
context="{'group_by': 'state'}"/>
<!-- Group by date with granularity -->
<filter name="group_month" string="Month"
context="{'group_by': 'create_date:month'}"/>
<filter name="group_week" string="Week"
context="{'group_by': 'create_date:week'}"/>
<filter name="group_day" string="Day"
context="{'group_by': 'create_date:day'}"/>
Multiple group by filters stack: Group by Status then by Month creates nested groups.
Search Defaults
Pre-activate filters when the view loads using action context:
<record id="action_maintenance_requests" model="ir.actions.act_window">
<field name="name">Maintenance Requests</field>
<field name="res_model">maintenance.request</field>
<field name="context">{
'search_default_filter_my': 1,
'search_default_filter_in_progress': 1,
'search_default_group_state': 1,
}</field>
</record>
The key format is search_default_<filter_name>. Set to 1 to activate, or a value for field searches: 'search_default_user_id': uid.
Favorite Filters
Users can save custom filter combinations as favorites. These are stored in ir.filters records. You can pre-create shared favorites via data XML:
<record id="filter_urgent_open" model="ir.filters">
<field name="name">Urgent Open Requests</field>
<field name="model_id">maintenance.request</field>
<field name="domain">[('priority','=','3'),
('state','not in',['done','cancelled'])]</field>
<field name="is_default">False</field>
<field name="user_id" eval="False"/>
</record>
Setting user_id to False makes it a shared/global filter visible to all users.
Linking Search Views to Actions
Associate a search view with an action:
<record id="action_maintenance" model="ir.actions.act_window">
<field name="search_view_id" ref="view_maintenance_search"/>
</record>
Common Patterns
- Archive filter:
domain="[('active', '=', False)]"to show archived records - Overdue filter:
domain="[('date_deadline', '<', context_today().strftime('%Y-%m-%d'))]" - Empty field filter:
domain="[('user_id', '=', False)]"for unassigned records - Negation filter:
domain="[('state', 'not in', ['done', 'cancelled'])]"for active records