Skip to main content
iVentureTeam

rotting_statusbar_duration

The rotting_statusbar_duration widget is the Odoo 19 form statusbar that shows how long a record spent in each stage and warns, on hover, when the record has gone stale.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 24, 2026Updated August 24, 20266 min read
Odoo 19 CRM opportunity form whose stage statusbar rendered by rotting_statusbar_duration shows time spent under each stage name and a rotting highlight on the current stage.
Technical namerotting_statusbar_duration
Field typesmany2one (stage field)
Viewsform
Modulemail, shipped with every Odoo database
Used in core3 occurrences across 3 modules: crm, hr_recruitment, project
VersionsOdoo 20.0, Odoo 19.0
No-code setupNo. Stage thresholds are no-code, but placing the widget needs XML
Alternativesstatusbar, rotting, badge_rotting

What the rotting statusbar does

Three widgets are stacked inside this one name. At the base sits the standard statusbar, the arrow-shaped stage bar at the top of pipeline forms. On top of it, the mail module's statusbar_duration adds time-in-stage labels, read from duration_tracking, a JSON field that mail.tracking.duration.mixin maintains with the seconds spent in each stage. rotting_statusbar_duration is the third layer, added in Odoo 19: it swaps in a template that highlights the rotting state and sets a hover tooltip that spells the problem out, with model-aware wording such as "This lead has been stuck in this stage for 12 days."

Because each layer spreads the descriptor of the one below, the whole statusbar feature set survives intact: stages stay clickable, folded stages collapse behind the ellipsis, statusbar_visible trims which stages show, and the form-only keyboard commands keep working. The only narrowing is the field type: the duration layers require a many2one stage field, while plain statusbar also accepts selection fields.

What this means for your team

A pipeline form answers two questions badly by default: how long has this record been with us, and where did that time go. This widget answers both in the header. Each past stage carries the time it consumed, and the current stage carries the time so far plus, past the stage's rot threshold, the stuck warning. For a sales manager doing deal reviews, the stage bar alone now tells the story that used to need the chatter archaeology.

The durations are also quietly one of the best process-mining tools in Odoo. If "Proposal" routinely eats three weeks while "Qualified" takes a day, the bar says so on every record, and patterns become visible to the people working the records rather than only to whoever builds the quarterly report. We have seen stage definitions get renegotiated within weeks of teams simply seeing these numbers.

The rot warning rides on the same per-stage threshold as the rest of the rotting family: rotting_threshold_days on the stage, 0 meaning off. Configure it where waiting is a process failure, leave it off where waiting is the process.

Supported options in Odoo 19

The widget declares nothing of its own; the options below are inherited from the statusbar descriptor through statusbar_duration, verified in statusbar_field.js and statusbar_duration_field.js on the Odoo 19.0 branch. The rotting layer adds behavior, not options.

OptionTypeWhat it does
clickablebooleanInherited from statusbar. When true, clicking a stage arrow moves the record to that stage and saves. Core CRM sets it explicitly.(default: true)
fold_fieldfield nameInherited from statusbar. Boolean field on the stage model marking stages that collapse behind the ellipsis, typically fold.

One knob is an attribute, not an option. Which stages are always visible on the bar is controlled by statusbar_visible="draft,posted" on the field element, inherited from the base statusbar. And one dependency is silent: the tooltip reads rotting_days from the record without declaring it, so the form must include that field, invisible is fine, for the day count to be correct. duration_tracking, by contrast, is a declared dependency and loads automatically.

Working examples

The core pattern, from the CRM opportunity form

<field name="stage_id" widget="rotting_statusbar_duration"
       options="{'clickable': '1', 'fold_field': 'fold'}"/>
<!-- elsewhere in the same form -->
<field name="is_rotting" invisible="1"/>
<field name="rotting_days" invisible="1"/>

Read-only bar with trimmed stages

<field name="stage_id" widget="rotting_statusbar_duration"
       options="{'clickable': False}"
       statusbar_visible="new,qualified,won"/>

Users see durations and the rot warning but change stages through buttons or the kanban instead of the bar.

Durations, tooltips, and the layer that actually rots

Where the durations really come from. duration_tracking is stored as a JSON map of stage id to seconds, recomputed from the record's tracking messages by mail.tracking.duration.mixin. The widget formats each entry twice, short for the label and long for its tooltip. A stage with no recorded time shows no duration label at all. If the bar shows no durations anywhere, the model is missing the mixin or the _track_duration_field does not point at the field you put the widget on.

What the rotting layer actually changes. Reading the source, the subclass does exactly two things: a template override that exposes the rotting state, and a title tooltip built from rotting_days with per-model wording for crm.lead, hr.applicant and project.task plus a generic fallback for custom models. Everything else, including which stages fold and the click-to-change behavior, is untouched inheritance.

Why there are two statusbar duration widgets. The mail module registers statusbar_duration for models that track durations but have no rotting configured, and rotting_statusbar_duration for the three pipelines that rot. Both accept the same options; the rotting variant is the one to use on any custom model where you enable the rotting mixin, otherwise the stuck state has no form-view representation.

Version compatibility

VersionStatusNotes
Odoo 20.0Partial / changedNot released. Same file and name, but the inherited duration logic gains live ticking and switches units. See below.
Odoo 19.0VerifiedIntroduced in 19.0 with the rotting family, on top of the existing statusbar_duration.
Odoo 18.0Not availableDoes not exist; statusbar_duration provides durations without rot alerts.
Odoo 17.0Not availableDoes not exist; statusbar_duration provides durations without rot alerts.
Odoo 16.0Not availableDoes not exist, and duration_tracking itself is not available either.

Upgrade note. The widget is new in Odoo 19. Forms upgraded from 18 keep working with statusbar or statusbar_duration; switching the stage field to rotting_statusbar_duration is a one-attribute change once the model rots.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. Findings below are from the public development branch, which is unstable until release; we re-verify after launch.

The widget file itself is unchanged on master, byte for byte, and keeps its name while its two siblings are renamed. What changes is inherited: the statusbar_duration base gains live tracking of the current stage. The duration_tracking payload grows an active-stage marker and start date, and the widget adds the elapsed time since that start client side, so the current stage's counter is up to date the moment the form opens instead of frozen at the last write.

The units contract changes with it. On master the stored tracking values are treated as minutes and multiplied back to seconds for formatting, where 19 reads them as seconds directly. Custom code that writes or reads duration_tracking raw will need attention during an Odoo 20 migration.

Common problems and fixes

SymptomCause and fix
No durations under any stage nameThe model lacks mail.tracking.duration.mixin, or _track_duration_field points at a different field than the widget is on. Add the mixin and set _track_duration_field to the stage field.
Tooltip says the record is stuck for undefined daysrotting_days is not loaded in the form; the widget reads it without declaring a dependency. Add rotting_days to the form, invisible="1" is enough.
No rot highlight although the record is oldThe stage's rotting_threshold_days is 0, or the model's rotting domain excludes the record, as CRM does for won leads. Set the threshold on the stage and check _get_rotting_domain overrides.
Users change stages by clicking the bar and should notclickable defaults to true. Set options="{'clickable': False}".
Some stages never show on the barThey are folded via fold_field, or statusbar_visible lists only a subset; the current stage always shows regardless. Adjust the fold flags on the stages or the statusbar_visible attribute.
Widget refuses a selection fieldThe duration layer narrows supportedTypes to many2one, unlike plain statusbar. Use statusbar for selection-based states.

Rotting statusbar vs the alternatives

WidgetBest forKey difference
rotting_statusbar_durationPipeline form headers that should show time in stage and rot alertsStatusbar plus durations plus the stuck-record tooltip, many2one stages only
statusbarPlain stage bars without time trackingNo durations, no rot state, also works on selection fields
rottingThe rot flag on kanban cardsDay pill only, registered for kanban views
badge_rottingThe rot flag in list viewsMany2one stage cell with an appended day pill

All three rotting widgets share one server-side feature and split by view. If you need a stage bar but no durations at all, plain statusbar remains the lightest choice.

Frequently asked questions

What is the difference between statusbar_duration and rotting_statusbar_duration?+
Both show the stage bar with time spent per stage from duration_tracking. The rotting variant, new in Odoo 19, additionally exposes the rotting state and sets a tooltip built from rotting_days, with wording adapted to leads, applicants and tasks. Options are identical because the descriptors are spread from one another.
Where do the times under the stage names come from?+
From duration_tracking, a JSON field maintained by mail.tracking.duration.mixin that maps each stage id to the seconds spent there, derived from the record's tracked stage changes. The widget only formats it; nothing is computed client side in Odoo 19.
Does rotting_statusbar_duration work on selection fields?+
No. The duration layer declares supportedTypes: ["many2one"], so it needs a many2one stage field. Plain statusbar is the widget that also accepts selection fields.
Can I use it on my own model?+
Yes: inherit mail.tracking.duration.mixin, point _track_duration_field at your stage field, give the stage model a rotting_threshold_days integer and your model a date_last_stage_update datetime. The widget then shows durations, and rot alerts once thresholds are set.
Why does the tooltip show the wrong number of days?+
The day count is read from the rotting_days field on the record, which the widget does not declare as a dependency. If the form does not load that field, the tooltip cannot know the number. Core forms include it invisibly.
What changes for this widget in Odoo 20?+
The file is currently unchanged on the development branch, but the inherited statusbar_duration logic changes: the current stage's duration ticks live using an active-stage start date in duration_tracking, and stored values switch from seconds to minutes. Nothing is final until the September 2026 release, and we re-verify then.

Stage bars that report time, not just state

We enable duration tracking and rot alerts on custom pipelines, from helpdesk queues to production stages, and we handle the duration_tracking contract change coming with Odoo 20 so your patched statusbars do not break. If your team argues about where deals stall, put the answer in the form header.

Plan my statusbar upgrade

How this page was produced

Verified by reading rotting_statusbar.js, statusbar_duration_field.js and statusbar_field.js on the Odoo 19.0 branch, tracing the full descriptor inheritance chain, and diffing all three files against the public development branch for the Odoo 20 section, which is where the live-ticking and minutes-based rework was found. Server behavior was read from mail_tracking_duration_mixin.py. Core usage was confirmed in the crm, hr_recruitment and project form archs. Found an inaccuracy? Tell us.