Skip to main content
iVentureTeam

statusbar_duration

statusbar_duration is the status bar that prints how long the record sat in every stage, fed by the chatter's tracking history. It only works on models that opt into a server-side mixin, and Odoo 20 rewrites its data contract.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 26, 2026Updated August 26, 20266 min read
Technical namestatusbar_duration
Field typesmany2one
Viewsform
Modulemail
Used in core2 occurrences across 1 module: project task views, with crm and others consuming it through subclasses
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0, Odoo 17.0
No-code setupNo. Requires XML plus a server-side mixin
Alternativesstatusbar, rotting_statusbar_duration, state_selection

What the Status with time widget does

The widget renders the familiar stage buttons across the top of a form, then asks the record for one extra piece of data: duration_tracking, a JSON object mapping stage ids to seconds spent. Every stage with a positive duration gets a muted suffix such as 5 days, with the precise long form in the tooltip.

The display name Odoo gives it is Status with time. It is a thin subclass of statusbar: one overridden method decorates the stage items, and templates inject the duration span on both the desktop buttons and the mobile dropdown. Everything else, clicking to change stage, folded stages, responsive folding, comes from the parent.

Two descriptor changes matter. supportedTypes is narrowed to many2one only, so unlike the base statusbar you cannot put it on a selection field. And it declares fieldDependencies on duration_tracking, which is why the durations appear without you adding anything to the view.

What this means for your team

Time in stage is the metric behind most pipeline questions: where do tasks stall, how long does qualification really take, which handoff is the bottleneck. This widget surfaces that number exactly where people already look, on the stage bar itself, without a report or a BI tool.

Because the durations are computed from the chatter's tracking history, they are audit-grade: the clock starts and stops on actual recorded stage changes, not on manual timestamps someone forgot to update. A task that bounced back to Doing twice shows the accumulated total.

The practical payoff is in reviews. A project lead scanning a task sees 12 days next to Waiting and asks the right question immediately. Teams we work with often pair it with stage discipline rules, since the numbers are only as honest as the stage changes behind them.

Supported options in Odoo 19

The widget declares no options of its own; the rows below are inherited from the base statusbar descriptor it spreads, verified in the Odoo 19.0 source of both files. The third row is technically an attribute on the field element, not an entry in options.

OptionTypeWhat it does
clickablebooleanInherited from statusbar. Whether clicking a stage button moves the record there; the click updates and saves immediately. Set it false when stages should only advance through workflow buttons.(default: true)(since Odoo 17.0)
fold_fieldfield nameInherited from statusbar. Boolean field on the stage model; stages with it set collapse into the trailing dropdown, mirroring folded kanban columns. Core passes 'fold' on project tasks.(since Odoo 17.0)
statusbar_visibleattribute (comma list)Field-element attribute, not an option: comma-separated values to show inline. The current stage is always displayed even when not listed. Inherited unchanged from the base widget.(since Odoo 17.0)

The duration is not an option. There is no switch controlling the time display; it appears whenever the record's duration_tracking JSON has a positive value for a stage. An empty JSON, which is what models without the mixin return, simply renders a plain status bar.

Working examples

How core applies it (project sharing task form)

<field name="stage_id" widget="statusbar_duration"
       options="{'clickable': '1', 'fold_field': 'fold'}"
       invisible="not project_id and not stage_id"/>

Enabling it on a custom model

The widget is the easy half. The model must opt into the mixin and point it at a tracked many2one:

# the field named in _track_duration_field must have tracking=True
class RepairOrder(models.Model):
    _name = "x_repair.order"
    _inherit = ["mail.tracking.duration.mixin"]
    _track_duration_field = "stage_id"

    stage_id = fields.Many2one("x_repair.stage", tracking=True)

Then in the form view:

<field name="stage_id" widget="statusbar_duration" options="{'clickable': '1'}"/>

If _track_duration_field is missing, names a non many2one, or the field is not tracked, the server compute raises a ValueError instead of failing silently; the error message spells out both requirements.

Where the durations come from

Understanding where the numbers come from explains every quirk of this widget.

Computed from the chatter, not stored. In Odoo 19, duration_tracking is a non-stored computed JSON. On every read the server walks mail.tracking.value rows, the same records that power the stage change lines in the chatter, and adds up the intervals per stage id. Delete the chatter messages and the durations change; that is by design, the tracking history is the single source of truth.

Keys are stage ids as strings. The JSON looks like {"1": 1230, "2": 2220}, seconds per many2one id. This is why supportedTypes is many2one only: a selection value has no id to key on.

The current stage shows accumulated past time only. In 19 the value for the stage the record is sitting in now covers completed intervals, frozen at the last stage change. The Odoo 20 branch is precisely about fixing this: the JSON gains an s key naming the active stage and a d key with its start datetime, and the widget adds the elapsed time client-side with luxon so the current stage ticks live.

Subclasses carry the feature elsewhere. Project's internal task form uses rotting_statusbar_duration, which inherits everything here and adds the stale-record pill. If you are reading durations on a task form, you are usually looking at the subclass.

Version compatibility

VersionStatusNotes
Odoo 20.0Partial / changedNot released. Stored JSON in minutes, live ticking, new s/d keys; see below.
Odoo 19.0VerifiedVerified against the shipped source; file identical to 17 and 18.
Odoo 18.0VerifiedSame source as 19. No XML changes needed.
Odoo 17.0VerifiedWidget and mixin introduced together in the mail module.
Odoo 16.0Not availableNeither the widget nor mail.tracking.duration.mixin exists.

Upgrade note. The widget arrived in Odoo 17 together with the mixin; there is nothing to migrate from 16, where neither exists. From 17 through 19 the file is unchanged, so views carry over untouched. Watch the 20 upgrade closely though; the data contract changes underneath (see below).

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026, and the development branch is not final until release. The changes visible there are substantial for this widget:

The JSON is stored and switches units. duration_tracking becomes a stored field with @api.depends on the tracked field, and values move from seconds to minutes. The widget compensates by multiplying by 60 before formatting.

Live ticking. The JSON gains two reserved keys: s, the active stage id, and d, the UTC datetime the stage started. The widget computes elapsed time client-side and adds it to the active stage, so the current stage's duration updates live instead of freezing at the last change.

A new guard. The rendering path requires the d key; a JSON without it, such as one produced by 19-era custom code, renders no durations at all. Custom modules that write duration_tracking directly will need to adopt the new shape. We re-verify against the shipped release.

Common problems and fixes

SymptomCause and fix
The status bar renders but no durations appearThe model does not use mail.tracking.duration.mixin, so duration_tracking is empty or absent. Inherit the mixin and set _track_duration_field to a tracked many2one on the model.
ValueError about the field needing to be Many2one with tracking=True_track_duration_field names a selection or an untracked field; the mixin's compute refuses both. Point it at a many2one with tracking=True; both conditions are mandatory.
The current stage's time seems frozenIn Odoo 17 to 19 the value only covers completed intervals up to the last stage change. Expected behavior; the live-ticking current stage arrives with the Odoo 20 rework.
Durations changed after cleaning up the chatterThe 19 compute reads mail.tracking.value history; deleting tracked messages deletes the underlying intervals. Keep tracking messages on models where time in stage matters; the history is the data.
Widget crashes or misbehaves on a selection fieldsupportedTypes is narrowed to many2one; JSON keys are stage ids. Use the plain statusbar for selection fields; durations need a many2one.

Status with time widget vs the alternatives

WidgetBest forKey difference
statusbar_durationStage bars where time spent per stage should be visible inlineAdds chatter-computed durations to each stage button; requires the tracking duration mixin
statusbarStandard stage navigation without time informationSame options and behavior, accepts selection fields too, no server-side requirements
rotting_statusbar_durationProject and CRM records that should flag themselves when staleSubclass of this widget that adds the rotting day-pill on top of the durations
state_selectionA compact per-record status bullet instead of a full stage barDropdown bullet for three-state flags, no stage history or durations

Pick by what the team needs to see: plain stages, use statusbar; stages with time, this widget; stages with time plus stale-record warnings, the rotting variant. All three share the same option surface, so switching is a one-word change in the view.

Frequently asked questions

What does the statusbar_duration widget do in Odoo?+
It renders the standard stage status bar and appends the time the record has spent in each stage, such as 5 days, computed from the chatter's tracking history. Odoo labels it Status with time.
Why do no durations show on my custom model?+
The numbers come from the duration_tracking JSON computed by mail.tracking.duration.mixin. Your model must inherit that mixin and set _track_duration_field to a many2one with tracking=True; without it the widget renders a plain status bar.
Can I use statusbar_duration on a selection field?+
No. Unlike the base statusbar, its supported types are narrowed to many2one only, because the duration JSON is keyed by the related record ids.
Where does Odoo store the time spent in each stage?+
In Odoo 17 to 19 it is not stored at all: the mixin recomputes it on read from mail.tracking.value records, the same data behind stage change lines in the chatter. The Odoo 20 development branch makes it a stored field, in minutes.
Does the current stage's duration update in real time?+
Not in Odoo 19; it shows accumulated completed intervals only. The Odoo 20 branch adds the active stage id and start datetime to the JSON so the widget can tick the current stage live in the browser.
Which core forms use statusbar_duration?+
In Odoo 19, the project sharing portal task form uses it directly, while the internal task form uses the rotting_statusbar_duration subclass, which inherits the duration display and adds staleness warnings.

Want to know where your pipeline loses days?

We wire time-in-stage tracking into your own models, tasks, tickets, orders, anything with stages, and build the dashboards that turn those durations into bottleneck answers. Odoo 16 to 19, including the mixin work the widget needs.

Get a pipeline review

How this page was produced

This page was verified by reading statusbar_duration_field.js and its template in the Odoo 19.0 mail module, the base statusbar_field.js in web, and mail_tracking_duration_mixin.py for the server contract, including the ValueError guard. The Odoo 20 section comes from diffing the same files against the public development branch, where both the client and the mixin change. Corrections welcome via our contact page.