What is ir.actions.server?#
ir.actions.server is the ORM model that backs every "Server Action" in Odoo. A server action is a named, reusable unit of code or configuration that can be triggered manually from a list view, called from another action, or fired automatically by base_automation.
Navigate to Settings → Technical → Actions → Server Actions (with developer mode on) to see and edit them directly.
Action types#
Each ir.actions.server record has a state field that determines what happens when the action runs:
| state | What it does |
|---|---|
code | Executes arbitrary Python code |
object_write | Writes field values to matched records |
multi | Runs multiple child server actions in sequence |
mail_post | Posts a message or sends an email |
followers | Adds or removes followers from the record |
next_activity | Schedules an activity |
The code type is the most powerful. When it executes, Odoo provides a local variable records (the current recordset) and env (the ORM environment).
Writing a Python code action#
From the UI, create a Server Action with state "Execute Python Code" and enter:
for rec in records:
rec.write({'state': 'confirmed'})
rec.message_post(body='Automatically confirmed by server action.')Available variables in the code context:
env-odoo.api.Environmentmodel- the model the action is bound torecords/record- the selected recordset (both names work)time,datetime,dateutil,timezone- standard Python modulesWarning-odoo.exceptions.Warningfor raising user-visible errorslog- a callable that writes to the server log
You cannot import arbitrary Python modules here. The execution context is sandboxed by design.
Binding a server action to a model#
To make a server action appear in the "Action" button dropdown of a list view, bind it to the model:
<record id="action_confirm_orders" model="ir.actions.server">
<field name="name">Confirm Selected Orders</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="binding_model_id" ref="sale.model_sale_order"/>
<field name="binding_type">action</field>
<field name="state">code</field>
<field name="code">
for rec in records:
rec.action_confirm()
</field>
</record>Setting binding_model_id is what creates the button in the list view. model_id tells the action which model it operates on (used for the records variable). They are often the same but can differ when an action on model A creates records in model B.
base_automation: automated triggers#
The base_automation module extends server actions with the base.automation model, which adds a trigger field. Navigate to Settings → Technical → Automation → Automated Actions.
Trigger types#
| Trigger | When it fires |
|---|---|
| On create | After create() completes |
| On update | After write() completes (can filter on changed fields) |
| On create or update | Either of the above |
| On delete | Before unlink() executes |
| Based on a time condition | At a scheduled time relative to a date field |
Time-based triggers#
Time-based automated actions are the most powerful and the most error-prone. Configure them like this:
- Trigger: Based on a time condition
- Before/After: After (e.g., 3 days after)
- Date field:
date_deadline - Filter: A domain that pre-selects records (e.g.,
[('state', '=', 'open')])
Odoo runs a cron job every hour that evaluates all time-based automated actions. Records that match the filter AND have a date field that crosses the threshold since the last run will execute the action.
Gotcha: The cron only looks at records whose date field falls between the previous run and the current run. If you create a time-based action after the relevant records already passed the threshold, those records will not trigger. You need to manually run the action or backfill.
Filtering on changed fields#
For "On update" triggers, restrict firing to only when specific fields changed:
<record id="auto_notify_on_priority_change" model="base.automation">
<field name="name">Notify on Priority Change</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="trigger">on_write</field>
<field name="filter_pre_domain">[('priority', '=', '0')]</field>
<field name="filter_domain">[('priority', '=', '1')]</field>
<field name="action_server_ids" eval="[(4, ref('action_notify_high_priority'))]"/>
</record>filter_pre_domain is evaluated on the record before the write. filter_domain is evaluated after. Together they let you trigger only on specific transitions.
Calling a server action programmatically#
Any ir.actions.server record can be executed from Python:
action = self.env.ref('my_module.action_confirm_orders')
action.with_context(active_ids=self.ids, active_model=self._name).run()This is useful for testing actions during development or triggering them from another model's method.
Debugging tips#
- Enable developer mode and use Settings → Technical → Automation → Automated Actions to see the last run time and any error logs.
- Add
log('debug message', level='debug')calls inside your code actions - they appear in the Odoo server log at the appropriate level. - If an automated action fires but seems to do nothing, check whether the
filter_domainis excluding your records. - Server actions run in the same transaction as the triggering operation. An unhandled exception in your action code will roll back the triggering write/create.

