Skip to content

Odoo Search Views, Filters, and Group By: Complete Developer Guide

DeployMonkey Team · March 23, 2026 12 min read

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