Skip to content

Odoo Domain Filters Cheat Sheet: Every Operator Explained

DeployMonkey Team · March 22, 2026 10 min read

Domain Syntax

A domain is a list of tuples: [('field', 'operator', 'value')]. Multiple tuples are combined with AND by default.

Operators

OperatorMeaningExample
=Equal[('state', '=', 'draft')]
!=Not equal[('state', '!=', 'done')]
>Greater than[('amount', '>', 1000)]
>=Greater or equal[('amount', '>=', 1000)]
<Less than[('date', '<', '2026-01-01')]
<=Less or equal[('quantity', '<=', 0)]
inIn list[('state', 'in', ['draft', 'sent'])]
not inNot in list[('state', 'not in', ['done', 'cancel'])]
likeCase-sensitive contains[('name', 'like', 'Acme')]
ilikeCase-insensitive contains[('name', 'ilike', 'acme')]
=likePattern match (case-sensitive)[('email', '=like', '%@gmail.com')]
=ilikePattern match (case-insensitive)[('email', '=ilike', '%@Gmail.com')]
child_ofIs child in hierarchy[('category_id', 'child_of', parent_id)]
parent_ofIs parent in hierarchy[('category_id', 'parent_of', child_id)]

Logic Operators

# AND (implicit — just list tuples)
[('state', '=', 'sale'), ('amount', '>', 1000)]
# Means: state = sale AND amount > 1000

# OR (prefix with '|')
['|', ('state', '=', 'draft'), ('state', '=', 'sent')]
# Means: state = draft OR state = sent

# NOT (prefix with '!')
['!', ('active', '=', True)]
# Means: NOT active = True (i.e., inactive records)

# Complex: (A AND B) OR (C AND D)
['|',
    '&', ('state', '=', 'sale'), ('amount', '>', 1000),
    '&', ('state', '=', 'draft'), ('is_urgent', '=', True),
]

Relational Field Traversal

# Traverse Many2one relations with dot notation
[('partner_id.country_id.code', '=', 'US')]
[('order_id.partner_id.is_company', '=', True)]
[('category_id.parent_id.name', '=', 'Services')]

Date Filters

from odoo import fields
from datetime import timedelta

today = fields.Date.today()

# This month
[('date', '>=', today.replace(day=1))]

# Last 30 days
[('date', '>=', today - timedelta(days=30))]

# This year
[('date', '>=', f'{today.year}-01-01')]

# Between dates
[('date', '>=', '2026-01-01'), ('date', '<=', '2026-03-31')]

Common Patterns

# Active records only
[('active', '=', True)]

# Current company
[('company_id', '=', self.env.company.id)]

# Current user's records
[('user_id', '=', self.env.uid)]

# Non-empty field
[('email', '!=', False)]

# Records without a parent
[('parent_id', '=', False)]

# Overdue invoices
[('invoice_date_due', '<', fields.Date.today()),
 ('payment_state', '!=', 'paid')]

# Products in stock
[('qty_available', '>', 0)]

# Search by name (case-insensitive)
[('name', 'ilike', 'search term')]

Using Domains in Python

# search()
records = self.env['sale.order'].search([
    ('state', '=', 'sale'),
    ('amount_total', '>', 1000),
], limit=10, order='date_order desc')

# search_count()
count = self.env['sale.order'].search_count([
    ('state', '=', 'sale'),
])

# Combining domains programmatically
from odoo.osv.expression import AND, OR
domain = AND([domain1, domain2])
domain = OR([domain1, domain2])

Using Domains in XML

<!-- In search view filters -->
<filter name="overdue" string="Overdue"
        domain="[('invoice_date_due', '&lt;', context_today().strftime('%Y-%m-%d'))]"/>

<!-- In record rules -->
<field name="domain_force">[('company_id', 'in', company_ids)]</field>

<!-- In action context -->
<field name="domain">[('state', '=', 'draft')]</field>

Tip: Use Claude Code for Complex Domains

Describe the filter in English: "Show me all invoices overdue by more than 30 days for customers in Europe." Claude Code generates the correct domain with proper OR/AND nesting and relational traversal.