Skip to content

Odoo Statusbar Widget and Workflow Buttons: Complete Implementation Guide

DeployMonkey Team · March 23, 2026 11 min read

The Statusbar Widget

The statusbar widget is the horizontal progress indicator at the top of Odoo form views. It shows the current state and lets users click to advance through stages. Combined with action buttons, it creates intuitive workflow interfaces like those in Sales Orders, Purchase Orders, and Invoices.

Basic Statusbar Setup

A statusbar requires a Selection field and a form view header:

# models/ticket.py
class SupportTicket(models.Model):
    _name = 'support.ticket'
    _description = 'Support Ticket'

    state = fields.Selection([
        ('new', 'New'),
        ('assigned', 'Assigned'),
        ('in_progress', 'In Progress'),
        ('resolved', 'Resolved'),
        ('closed', 'Closed'),
    ], default='new', required=True, tracking=True, copy=False)
<!-- views/ticket_views.xml -->
<form>
  <header>
    <field name="state" widget="statusbar"
           statusbar_visible="new,assigned,in_progress,resolved,closed"/>
  </header>
  <sheet>
    <!-- form content -->
  </sheet>
</form>

Controlling Visible States

The statusbar_visible attribute controls which states appear in the progress bar. States not listed are hidden but still valid. This is useful for hiding terminal states like "cancelled":

<field name="state" widget="statusbar"
       statusbar_visible="new,assigned,in_progress,resolved,closed"/>
<!-- 'cancelled' state exists but is hidden from the progress bar -->

Clickable Statusbar

By default, users can click on any visible state to jump directly to it. This may not be appropriate for workflows requiring sequential transitions. The clickable attribute is set to 1 by default. You can restrict it conditionally:

<field name="state" widget="statusbar"
       statusbar_visible="new,assigned,in_progress,resolved"
       options="{'clickable': '1'}"/>

To enforce sequential transitions, use workflow buttons instead of making the statusbar clickable, and handle validation in Python.

Workflow Buttons in the Header

Action buttons in the <header> element appear next to the statusbar. Use invisible to show them only in the appropriate state:

<header>
  <button name="action_assign" type="object"
          string="Assign" class="btn-primary"
          invisible="state != 'new'"/>
  <button name="action_start" type="object"
          string="Start Work" class="btn-primary"
          invisible="state != 'assigned'"/>
  <button name="action_resolve" type="object"
          string="Resolve" class="btn-primary"
          invisible="state != 'in_progress'"/>
  <button name="action_close" type="object"
          string="Close" class="btn-primary"
          invisible="state != 'resolved'"/>
  <button name="action_cancel" type="object"
          string="Cancel" class="btn-secondary"
          invisible="state in ('closed', 'cancelled')"/>
  <button name="action_reopen" type="object"
          string="Reopen" class="btn-secondary"
          invisible="state not in ('resolved', 'closed')"/>
  <field name="state" widget="statusbar"
         statusbar_visible="new,assigned,in_progress,resolved,closed"/>
</header>

Button Types

Odoo supports several button types in form headers:

  • type="object" — calls a Python method on the model by name
  • type="action" — triggers an ir.actions record (wizard, window action, etc.)
<!-- Call Python method -->
<button name="action_resolve" type="object" string="Resolve"/>

<!-- Open wizard -->
<button name="%(module.wizard_action_id)d" type="action"
        string="Add Note"/>

Button Styling

Use Bootstrap button classes for consistent styling:

  • btn-primary — blue, for the main action in each state
  • btn-secondary — gray, for alternative actions
  • btn-success — green, for completion actions
  • btn-danger — red, for destructive actions
<button name="action_resolve" type="object"
        string="Mark Resolved" class="btn-success"
        invisible="state != 'in_progress'"/>
<button name="action_cancel" type="object"
        string="Cancel Ticket" class="btn-danger"
        invisible="state in ('closed', 'cancelled')"
        confirm="Are you sure you want to cancel this ticket?"/>

The confirm attribute shows a confirmation dialog before executing the action.

Python Transition Methods

Each button calls a Python method that validates and performs the transition:

def action_assign(self):
    for ticket in self:
        if not ticket.user_id:
            raise UserError(
                'Please assign a user before changing status.')
    self.write({'state': 'assigned'})

def action_start(self):
    self.write({
        'state': 'in_progress',
        'start_date': fields.Datetime.now(),
    })

def action_resolve(self):
    self.write({
        'state': 'resolved',
        'resolution_date': fields.Datetime.now(),
    })

def action_close(self):
    for ticket in self:
        if not ticket.resolution_notes:
            raise UserError(
                'Please add resolution notes before closing.')
    self.write({'state': 'closed'})

def action_cancel(self):
    self.write({'state': 'cancelled'})

def action_reopen(self):
    self.write({
        'state': 'in_progress',
        'resolution_date': False,
    })

Many2one Statusbar (Stages)

For pipeline-style views where stages are configurable records (not hardcoded), use a Many2one field with the statusbar widget:

stage_id = fields.Many2one(
    'support.ticket.stage', string='Stage',
    group_expand='_read_group_stage_ids',
    tracking=True, copy=False)

@api.model
def _read_group_stage_ids(self, stages, domain):
    return stages.search([], order='sequence')
<field name="stage_id" widget="statusbar"
       options="{'clickable': '1'}"/>

The _read_group_stage_ids method ensures all stages appear in kanban columns even if no records are in that stage.

Conditional Button Groups

Use the groups attribute to restrict buttons by security group:

<button name="action_approve" type="object"
        string="Approve" class="btn-primary"
        invisible="state != 'submitted'"
        groups="module.group_manager"/>

Only users in the group_manager group see the Approve button.

Best Practices

  • Always use tracking=True on state fields so changes appear in chatter
  • Set copy=False to prevent duplicated records from inheriting the state
  • Use confirm attribute on destructive actions like Cancel
  • Show only one primary button per state to guide users through the workflow
  • Place the statusbar field last in the header, after all buttons