Domains in Odoo are expressions that filter records in database queries, search views, or field definitions. They define conditions similar to SQL WHERE clauses but written in Odoo’s domain syntax. When working with Odoo 19, one of the most powerful concepts you’ll encounter is domains. They control what users see, how records are filtered, and what data becomes available in forms, lists, and reports. Whether you're customizing views, building modules, or defining security rules, understanding domains is essential.
A domain is a filtering expression used to show only specific records based on conditions. It follows a simple structure: [('field_name', 'operator', value)]. Domains can be used in: Views (XML), Python (search, search_count, read_group), Actions, Record rules, and Context-driven logic.
Basic Domain Filters
In Odoo 19, basic domain filters are the simplest and most commonly used way to restrict records. They are used to decide which records should be shown, selectable, or processed in views, actions, and searches. Think of a domain as Odoo’s version of a SQL WHERE clause, written in a readable Python-like syntax.
A basic domain is written as a list of tuples. Each tuple has three elements:
('field_name', 'operator', value)
- field_name > the field you want to filter on
- operator > how the comparison is done
- value > the value to compare with
Example:
[('state', '=', 'draft')]
Shows only records where the state is in draft.
Common Operators in Basic Domains
| = | Equal | ('state', '=', 'sale') |
| != | Not equal | ('state', '!=', 'cancel') |
| > | Greater than | ('amount_total', '>', 5000) |
| < | Less than | ('qty', '<', 10) |
| >= | Greater or equal | ('date_order', '>=', '2025-01-01') |
| <= | Less or equal | ('date_order', '<=', '2025-01-31') |
| in | In list | ('state', 'in', ['draft', 'sent']) |
| not in | Not in list | ('state', 'not in', ['cancel']) |
| ilike | Case-insensitive contains | ('name', 'ilike', 'customer') |
By default, Odoo combines multiple conditions using AND.
Example:
[('state', '=', 'sale'), ('amount_total', '>', 1000)]
Show records where state is ‘sale’ AND amount_total is greater than 1000. No special operator is needed for AND in basic domains.
Complex Logical Operators
When basic domain filters are not enough, complex logical operators allow you to build powerful conditions using OR, AND, and NOT logic. These operators help model real business rules directly in Odoo without writing SQL.
In Odoo 19, logical operators work the same across XML, Python, actions, and record rules.
Logical Operators
Logical operators are written before the conditions they apply to (prefix notation).
- OR Operator (|): Use | when any one of the conditions can be true.
Example: ['|', ('state', '=', 'draft'), ('state', '=', 'sent') ]
Here Show orders where the state is draft OR sent.
- AND Operator (&): Although AND is implicit in basic domains, you can explicitly use & when mixing with OR or NOT.
Example: ['&', ('state', '=', 'sale'), ('partner_id.country_id.code', '=', 'US') ]
Here orders must be confirmed AND belong to US customers.
- NOT Operator (!): Use ! to negate a condition.
Example: ['!', ('state', '=', 'cancel') ]
Here exclude Cancelled Records.
- Combining AND + OR (Nested Logic): This is where complex operators become powerful.
Example:
['|',
('amount_total', '>', 10000),
'&',
('state', '=', 'sale'),
('partner_id.is_company', '=', True)
]
It shows records where Amount > 10,000, OR State is sale AND customer is a company.
Context-Based Domains
Context-based domains allow domains to change dynamically based on runtime information such as: Current user, Current company, Default values and Values passed from actions or buttons. They make domains smart and flexible, without hardcoding values.
The context is a dictionary (key: value) that carries temporary information across: Views, Actions, Wizards and Records. You can access it using: context.get('key') in XML and self.env.context.get('key') in Python.
Example:
<field name="partner_id"
domain="[('company_id', '=', context.get('company_id'))]"/>
It shows only partners belonging to the active company.
Using Default Values from Context :
This is common in: Wizards, One2many popup forms and Smart buttons.
Example: Filter Based on Default Partner
<field name="contact_id"
domain="[('parent_id', '=', context.get('default_partner_id'))]"/>
Context-Based Domain in Python:
This is useful when: Calling methods from actions, Handling logic from buttons and Working inside wizards.
Example:
company_id = self.env.context.get('company_id')
orders = self.env['sale.order'].search([
('company_id', '=', company_id)
])Filtering by Current User:
XML
<field name="task_id"
domain="[('user_id', '=', uid)]"/>
Python
self.env['project.task'].search([
('user_id', '=', self.env.user.id)
])
Passing Context from an Action:
Action Definition
<record id="action_my_tasks" model="ir.actions.act_window">
<field name="name">My Tasks</field>
<field name="res_model">project.task</field>
<field name="view_mode">tree,form</field>
<field name="context">{'default_user_id': uid}</field>
</record>
Using It in Domain
<field name="user_id"
domain="[('id', '=', context.get('default_user_id'))]"/>
Context with Multi-Company Domains:
<field name="product_id"
domain="['|',
('company_id', '=', False),
('company_id', '=', context.get('company_id'))
]"/>
Meaning: Allows shared products + company-specific products.
Dynamic Domains in Views
Dynamic domains in views allow Odoo to change available records in real time based on user input. Unlike static or context-based domains, these domains react when a field value changes—making forms smarter and more user-friendly.
In Odoo 19, dynamic domains are commonly implemented using: XML field references or @api.onchange methods.
A dynamic domain updates automatically when: A user selects or changes a field, Another field depends on that value. They are mainly used in: Form views, Wizards and One2many popup forms.
For example: Selecting a country filters available states.
XML-Based Dynamic Domains
Odoo can dynamically evaluate domains in XML using field names directly.
Example: Filter States by Selected Country
<field name="country_id"/>
<field name="state_id"
domain="[('country_id', '=', country_id)]"/>
How It Works: When country_id changes, Odoo automatically recalculates the domain for state_id. And no Python code required.
Dynamic Domains Using @api.onchange
Use @api.onchange when: Logic is complex or Multiple fields affect the domain or Conditions cannot be expressed in XML.
Example: Filter Products by Category and Company
@api.onchange('category_id', 'company_id')
def _onchange_category_company(self):
domain = []
if self.category_id:
domain.append(('categ_id', '=', self.category_id.id))
if self.company_id:
domain += ['|',
('company_id', '=', False),
('company_id', '=', self.company_id.id)]
return {
'domain': {
'product_id': domain
}
}Dynamic Domains in Wizards
Dynamic domains are heavily used in wizard forms.
Example: Filter Employees by Department
@api.onchange('department_id')
def _onchange_department_id(self):
return {
'domain': {
'employee_id': [('department_id', '=', self.department_id.id)]
}
}Security Domains
Security domains, implemented through record rules, are used to control which records a user can access in Odoo. Unlike view-level domains (which only affect the UI), security domains are enforced at all levels: Form views, List views, Search queries, Reports, and API calls. In Odoo 19, record rules are a critical part of building secure and reliable ERP systems.
Record Rules
A record rule is a domain applied automatically to a model that restricts access based on conditions. It defines: Which records can this user read, write, create, or delete?
Basic Record Rule Example:
<record id="rule_user_own_records" model="ir.rule">
<field name="name">Own Records Only</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
</record>
Meaning - A user can only access records where: user_id = current user
Combining Conditions:
['&',
('user_id', '=', user.id),
('state', '!=', 'cancel')
]
Here Own Records + Not Cancelled
Using Current User in Domains
You can use special variables:
- user.id > current user ID
- user.company_id.id > current company
- uid > also current user ID
How Record Rules Work Internally
Record rules automatically applied to all ORM operations. It Combined with other rules using AND logic. And cannot be bypassed by normal code.
If multiple rules exist: They are combined using AND Access is granted only if all rules pass. This can accidentally restrict too much data if not designed carefully.
Multi-Company Domains
In Odoo 19, multi-company support allows a single database to manage multiple companies. Multi-company domains ensure that users only see relevant data based on their company access, while still allowing shared records where needed. They are essential for maintaining data isolation, consistency, and security in real-world ERP systems.
In a multi-company setup: Each company has its own data (sales, invoices, employees, etc.) and some data may be shared (products, contacts). Domains help answer: Which records should this user see in this company?
Example - Restrict to Current Company :
<field name="domain">[('company_id', '=', company_id)]</field>Shows only records belonging to the active company.
['|', ('company_id', '=', False), ('company_id', '=', self.env.company.id)]This is the most common pattern in Odoo.
Multi-Company Domain in Views
<field name="product_id"
domain="['|',
('company_id', '=', False),
('company_id', '=', company_id)
]"/>
Filters product selection based on company context.
Multi-Company in Record Rules
<record id="rule_multi_company" model="ir.rule">
<field name="name">Multi-Company Access</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="domain_force">
['|',
('company_id', '=', False),
('company_id', '=', user.company_id.id)
]
</field>
</record>
Ensures users cannot access other companies' data.
Odoo supports users working in multiple companies at once.
[('company_id', 'in', self.env.context.get('allowed_company_ids'))]Show records from all companies the user has selected in the UI. Very important in: Accounting, Inventory and Consolidated reporting.
To read more about Overview of Basic Domain Filters in Odoo19, refer to our blog Overview of Basic Domain Filters in Odoo19.