All posts
Developer10 min read

ir.actions.server and automated actions in Odoo: triggers, Python code, and base_automation

Server actions and automated actions let you execute Python code, send emails, and mutate records in response to business events - without deploying a custom module. This guide covers the ir.actions.server model, all action types, and how base_automation wires triggers to actions.

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:

stateWhat it does
codeExecutes arbitrary Python code
object_writeWrites field values to matched records
multiRuns multiple child server actions in sequence
mail_postPosts a message or sends an email
followersAdds or removes followers from the record
next_activitySchedules 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:

python
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.Environment
  • model - the model the action is bound to
  • records / record - the selected recordset (both names work)
  • time, datetime, dateutil, timezone - standard Python modules
  • Warning - odoo.exceptions.Warning for raising user-visible errors
  • log - 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:

xml
<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#

TriggerWhen it fires
On createAfter create() completes
On updateAfter write() completes (can filter on changed fields)
On create or updateEither of the above
On deleteBefore unlink() executes
Based on a time conditionAt 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:

xml
<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:

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_domain is 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.

Try ERPeek on your own Odoo module - ask questions, scaffold tests, and explore your codebase in plain language.

Get started free