Odoo Development
Odoo Server Actions and Automated Actions in Code
Server actions are Odoo's way of expressing behaviour as data rather than code. Combined with automated rules they cover a lot of ground with no module at all, which is genuinely useful and quietly becomes a maintenance problem the moment nobody remembers they exist.
Part of our guide to Odoo Development
What a server action is
An ir.actions.server record is behaviour stored as data. The state field picks the type, and there are six:
object_write, Update Record: set field values on the records it receivesobject_create, Create Record: create a new record, optionally linked backobject_copy, Duplicate Recordcode, Execute Code: run a Python snippetwebhook, Send Webhook Notification: post to an external URLmulti, Multi Actions: run several server actions in sequence
Multi Actions is the one that makes the rest useful. Real automations are usually two or three steps, and chaining is how you get there without code.
Defining one in a module
Server actions are records, so a module ships them like any other data:
Flag large order code for record in records: if record.amount_total > 50000: record.write({"priority": "1"})
Inside a code action you get env, model, record for a single record, records for the set, and UserError for stopping with a message. The snippet is evaluated in a restricted namespace, so imports and file access are not available.
Keep these to one line calling a real method whenever the logic is more than trivial. A method is version controlled, reviewable, testable and greppable. A code field is none of those.
Automated rules add the trigger
A server action on its own runs when something calls it. An automated rule attaches a trigger. The useful ones:
on_create, when a record is createdon_create_or_write, on creation and on every updateon_unlink, on deletionon_time, relative to a date field, such as three days before an expiryon_stage_setandon_priority_set, when a specific field reaches a specific value
One note for anyone reading older examples: on_write is deprecated in favour of on_create_or_write. It still appears in the selection, and new rules should not use it.
on_time rules are evaluated by a scheduled action, so they inherit its timing. A rule set for "one day before" fires when the cron next runs, not at a precise moment.
Where this goes wrong
Automated rules are configuration, and configuration is invisible in a way code is not.
They are not in your repository. A rule created in production exists only there. It does not appear in a diff, it is not reviewed, and a fresh environment built from your code does not have it.
They fire on every matching write, including yours. A rule updating a field on on_create_or_write can trigger itself, and the resulting loop is only bounded by whatever recursion protection happens to apply.
They are invisible when debugging. A field changing with no code explaining it is a genuinely difficult afternoon, especially for whoever inherits the system.
They run on restored databases. A copy of production has every rule active, so a test database sends webhooks to real endpoints.
Given all that, the rule of thumb is about ownership rather than complexity. If a business user genuinely maintains it, a rule is right, and the visibility cost is the price of them not needing a developer. If a developer maintains it, it belongs in a module, where it gets version control, review and tests.
Ship rules as data if you must
When a module needs an automated rule, ship it as a record and set noupdate deliberately. Without it, every module upgrade resets the rule to your shipped definition and discards local adjustments. With it, your later improvements never reach existing databases.
Neither is universally right, which is exactly why the choice should be explicit. The mechanics are covered in data files and XML IDs.
Auditing what exists
On any inherited system, list the automated rules and server actions early. It is one query and it regularly explains behaviour nobody could locate in code.
Do it before an upgrade too. Rules referencing fields that were renamed between versions do not fail loudly, they simply stop matching, and the automation everyone relied on quietly stops happening.
FAQ
Questions, answered.
What types of server action does Odoo support?
Six: Update Record, Create Record, Duplicate Record, Execute Code, Send Webhook Notification and Multi Actions. Multi Actions chains other server actions in sequence, which is how anything non-trivial is assembled without writing Python.
What is the difference between a server action and an automated action?
A server action defines what happens. An automated rule defines when, by attaching a trigger such as on creation, on update or based on a date field. The server action can also be run manually from a menu with no rule at all.
Should business logic live in server actions or in a module?
Anything a business user genuinely maintains can live in a rule. Anything a developer maintains should be a module, because code in a database field has no version control, no review, no tests and does not travel between environments with your repository.
Keep reading
More on Odoo Development.
The full guide, plus the other articles in this cluster.
With CODEerts
Need something built properly?
We are certified Odoo partners. We build custom Odoo modules that extend the framework rather than fight it, and we publish our own apps on the Odoo Store.
See how we can helpBook a callReady to make Odoo work the way your business does?
Book a free callCODEerts is a team of certified Odoo partners and full-stack engineers. We implement, customise and support Odoo ERP, then build the software around it.