How to Extend Workflows in Odoo 19: Complete Tutorial Guide
Extending workflows in Odoo 19 gives developers and administrators the power to shape every stage of a document's lifecycle — from draft to approval to completion. This complete tutorial covers how to add new states, implement transition methods, extend views with XPath, configure no-code automation rules, and leverage AI-powered server actions in Odoo 19 Enterprise. Whether you are a beginner guide reader looking for fundamentals or an experienced developer seeking advanced techniques, this step by step guide has everything you need.
What You'll Learn:
- How workflows function in Odoo 19 using state fields and Python transition methods
- The difference between Simple and Approval workflow patterns
- How to add custom states using selection_add model inheritance
- How to write transition methods that preserve Odoo's base behavior with super()
- How to extend existing views with XPath to add context-aware buttons
- How to configure no-code Automation Rules from the Settings menu
- How to use AI-powered workflow extensions available in Odoo 19 Enterprise
- Best practices for securing workflow transitions with access rules
How Workflows Work in Odoo 19
Odoo 19 no longer uses the legacy XML workflow engine. Instead, workflows are driven entirely by state fields, Python methods for transitions, UI buttons, and access permissions defined in your model. This approach is simpler to extend because every state is a plain Python string and every transition is a standard ORM method.
The core of any Odoo workflow is the state field — a fields.Selection that defines every possible stage a document can occupy. From there, buttons in the form view call Python methods that validate the current state, apply business logic, and update the record to the next stage. Access rights and record rules then control who can trigger which transitions, creating a complete, auditable workflow engine.
# Standard Odoo 19 state field pattern
state = fields.Selection([
('draft', 'Draft'),
('confirm', 'Confirmed'),
('done', 'Done'),
('cancel', 'Cancelled'),
], default='draft', string='Status', tracking=True)
# Transition method example
def action_confirm(self):
for record in self:
record.state = 'confirm'
Two Core Workflow Patterns in Odoo 19
Before extending any workflow, it helps to recognise which of the two fundamental patterns the existing model uses. Each pattern has different implications for how you add new states and transitions.
Simple Workflows
Transfer entities through sequential stages without requiring authorization at each step. Ideal for high-volume operations such as picking orders or delivery slips where speed matters and every user has equal authority. Minimal coding is required and permissions are managed at the model level.
Approval Workflows
Require explicit authorization before a document can advance to the next state. Used in compliance-sensitive flows such as purchase order approval or expense validation. Extension involves adding intermediate states and restricting transition methods to specific user groups or roles.
State Fields
The backbone of every Odoo 19 workflow. fields.Selection holds all valid states, default sets the starting point, and tracking=True records every change in the chatter. Adding states is done via selection_add in an inherited model rather than redefining the entire field.
Transition Methods
Python methods that handle the business logic for moving between states. The critical rule is to always call super() inside overridden transitions so that Odoo's built-in side effects — email notifications, stock moves, journal entries — are preserved. Never skip the super() call.
Step by Step Guide: Extending Workflows in Odoo 19
This beginner guide walks you through the complete process of extending an existing Odoo 19 workflow. Each step builds on the previous one, taking you from understanding the base model all the way to securing transitions with access rules. Follow these steps in order for the best results.
Understand the Existing Workflow State Model
Before writing a single line of code, open the Odoo shell or read the source model to list every state the field already defines. Run self.env['sale.order']._fields['state'].selection in the Odoo shell to view all states. Knowing the full state list prevents you from accidentally duplicating a state name or inserting a transition that jumps over a required intermediate stage.
Plan Your Workflow Extension Requirements
Document three things before coding: (1) the new states you need and where they fit in the flow, (2) the transition methods that will move records into and out of each new state, and (3) which user groups are permitted to trigger each transition. A written diagram — even a simple list — prevents scope creep and makes code review straightforward. Decide whether you need a Simple extension (just an extra state) or an Approval extension (an intermediate hold state that requires a manager to clear).
Add New States via Python Model Inheritance
Create a new model file in your custom module using _inherit to inherit the target model. Use selection_add on the state field to append your new states without overwriting the existing ones. Always include an ondelete mapping for each new state so Odoo knows what to do during module uninstall or upgrade. This is the safest way to extend state machines in Odoo 19 and is upgrade-proof across community and enterprise editions.
Implement Custom Transition Methods with super()
Override existing transition methods (such as action_confirm) to intercept records that meet your new condition and redirect them to the new state instead of continuing to the standard next stage. For all records that do not match your custom condition, call return super().action_confirm() to let the original flow proceed unmodified. Add brand new methods (such as action_manager_approve) for transitions that only make sense in your new state, with a guard that raises a UserError if called in the wrong state.
Extend Views Using XPath Inheritance
Create a new view record that inherits the original form view via inherit_id. Use XPath expressions to locate existing buttons (such as //button[@name='action_confirm']) and insert your new button immediately after them with position="after". Set the invisible attribute on your new button so it only appears when the record is in your custom state. This ensures the UI stays clean and context-appropriate for every stage of the workflow.
Configure No-Code Automation Rules
Navigate to Settings > Technical > Automation > Automation Rules and click New. Choose the model, set the trigger (time-based, record creation, field value change, or stage change), define filter conditions, and select the server action to execute. Available server actions include creating or updating records, sending email or SMS, adding followers, creating activities, and executing chained actions. This approach requires no Python code and is ideal for business users who need simple trigger-based workflow extensions.
Apply Best Practices and Secure Access Controls
Add ir.rule records in your module data to restrict which users can trigger sensitive transitions. Set noupdate="1" on custom XML data to prevent Odoo upgrades from reverting your records. Apply invisible domain conditions to every new button. Wrap data files in a <data noupdate="1"> tag in your manifest's data list. For Odoo 19 Enterprise, enable the AI Server Actions feature to allow describing automation in plain English — Odoo's AI layer translates it into a working server action automatically.
Complete Model Inheritance Code Example
The following example extends sale.order to add a Manager Approval state that is triggered automatically when a sales order exceeds 10,000 in total value. Orders below the threshold bypass the new state and confirm immediately. This pattern is the foundation of any approval workflow extension in Odoo 19.
# models/sale_order.py
from odoo import models, fields, _
from odoo.exceptions import UserError
class SaleOrderExtended(models.Model):
_inherit = 'sale.order'
# Append new state — do NOT redefine the entire field
state = fields.Selection(
selection_add=[('manager_approval', 'Manager Approval')],
ondelete={'manager_approval': 'set default'}
)
def action_confirm(self):
"""Intercept confirmation for high-value orders."""
for order in self:
if order.state != 'manager_approval' and order.amount_total > 10000:
order.state = 'manager_approval'
return # Hold here; do not call super()
# All other orders proceed through the standard flow
return super().action_confirm()
def action_manager_approve(self):
"""Manager explicitly approves a held order."""
for order in self:
if order.state != 'manager_approval':
raise UserError(_(
"Only orders in Manager Approval state can be approved here."
))
return super().action_confirm()
Extending Views with XPath Inheritance
After adding the new state and method, the corresponding UI button must be added to the sale order form. The XPath selector targets the existing Confirm button by its method name and inserts the Manager Approve button directly after it. The invisible attribute ensures the button only renders when the order is in the manager_approval state.
<!-- views/sale_order_views.xml -->
<record id="view_sale_order_form_extended" model="ir.ui.view">
<field name="name">sale.order.form.extended</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<!-- Target the existing Confirm button by method name -->
<xpath expr="//button[@name='action_confirm']" position="after">
<!-- Insert Manager Approve button; only visible in the new state -->
<button name="action_manager_approve"
string="Manager Approve"
type="object"
class="btn-success"
invisible="state != 'manager_approval'"/>
</xpath>
</field>
</record>
No-Code Automation Rules: Supported Trigger Types
Automation Rules in Odoo 19 support several trigger types, each suited to a different class of workflow extension. Understanding which trigger to use prevents over-engineering and keeps rules maintainable by non-developers.
| Trigger Type | When It Fires | Typical Use Case |
|---|---|---|
| Record Creation | When a new record is saved | Auto-assign a salesperson or send a welcome email |
| Field Value Change | When a specific field is modified | Send notification when a deal stage changes |
| Time-Based | After a set number of days/hours | Archive records inactive for 7 days |
| Stage Change | When a kanban stage is updated | Create an activity when a lead enters Qualified |
| Record Update | On every write to a record | Sync a computed field to an external system |
Best Practices for Extending Workflows in Odoo 19
Following established best practices prevents upgrade breakage, unintended data loss, and access control gaps. The table below summarises the five rules every developer should follow when extending Odoo 19 workflows.
| Best Practice | Why It Matters | Implementation |
|---|---|---|
| Always call super() | Preserves email notifications, stock moves, and journal entries | Add return super().method_name() at the end of overridden methods |
| Use selection_add | Prevents overwriting existing states during upgrades | Use selection_add=[('new_state', 'Label')] with ondelete mapping |
| Set invisible conditions | Avoids UI clutter and prevents accidental button clicks | Add invisible="state != 'new_state'" to each button |
| Wrap data in noupdate="1" | Stops Odoo upgrades from reverting your custom XML records | Use <data noupdate="1"> in your data XML file |
| Add ir.rule access rules | Restricts sensitive transitions to authorised user groups | Define record rules in security/ir.rule.xml referencing the transition method |
Critical: Never Skip super() in Overridden Transitions
Skipping the super() call in an overridden transition method silently breaks Odoo's built-in side effects. A sale order that does not call super().action_confirm() will not create the corresponding journal entries, will not reserve stock, and will not send the order confirmation email — even though the state field changes to 'sale'. These bugs are hard to spot in testing but cause serious data integrity issues in production.
AI-Powered Workflow Extension (Odoo 19 Enterprise)
Odoo 19 Enterprise introduces AI Server Actions that let you describe automation logic in plain English. For example: "If a manufacturing order is marked In Progress and materials are ready, set the associated sales opportunity to Negotiation stage." Odoo's AI layer interprets this description and generates the underlying server action automatically. Enable the feature under Settings > Technical > Automation > AI Server Actions, then write your rule in natural language. This is especially useful for complex multi-model rules that would otherwise require bespoke Python code.
Frequently Asked Questions
Can I extend Odoo 19 workflows without writing any Python code?
Yes. Odoo 19's Automation Rules (Settings > Technical > Automation Rules) let you create trigger-based rules with no coding. You choose the model, the trigger event, filter conditions, and the server action to execute — all through the UI. Supported actions include sending emails, creating records, updating fields, adding followers, and creating activities.
What is the correct way to add a new state to an existing Odoo model?
Use selection_add in an inherited model class. Never redefine the entire state field, as this would overwrite all existing selection values and break the original workflow. Always include an ondelete dictionary entry for your new state so Odoo can safely remove it during uninstall or upgrade without leaving orphaned records.
Why is calling super() so important in Odoo 19 transition methods?
Odoo's base modules attach important side effects to transition methods — stock reservation, journal entry creation, email notifications, and chatter logging. By calling super(), your custom code invokes all those upstream handlers automatically. Skipping it means those side effects never run, causing data integrity issues that are often invisible until they surface as discrepancies in inventory or accounting reports.
How do I restrict a custom workflow transition to specific user groups?
Add an ir.rule record in your module's security XML file that limits which users can write records in your new state. Alternatively, use a Python guard inside the transition method that checks self.env.user.has_group('your_module.group_manager') and raises a UserError if the user is not authorised. Both approaches are upgrade-safe when the data file uses noupdate="1".
Is the AI-powered workflow extension available in Odoo 19 Community?
No. AI Server Actions are an Odoo 19 Enterprise feature. Community edition users can still create powerful automation using standard Automation Rules, Python server actions, and model inheritance. The AI layer is an optional convenience for Enterprise customers who want to describe business logic in plain English rather than writing Python code.
Need Help Extending Odoo 19 Workflows?
Our certified Odoo developers can design and implement custom workflow extensions, approval flows, and automation rules tailored to your business processes — from simple state additions to fully-automated AI-powered pipelines.
About the author
Founder & Odoo Practice Lead, Braincuber Technologies
Founder of Braincuber. Has scoped and shipped 500+ Odoo implementations for US mid-market and global brands. Takes every founder call personally — no SDR layer between buyers and the people building the system.
