Skip to main content
iVentureTeam

rotting

Odoo 19 marks records that sit too long in a pipeline stage as rotting. The rotting widget is the red day-count badge that appears on their kanban cards.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 24, 2026Updated August 24, 20267 min read
Technical namerotting
Field typesinteger (placed on rotting_days)
Viewskanban
Also registered askanban.rotting is the only registration; there is no base name
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. Configuring the stage threshold is no-code, but placing the widget needs XML
Alternativesbadge_rotting, rotting_statusbar_duration, remaining_days

What the rotting badge does

Odoo 19 introduced a pipeline hygiene feature the source calls rotting: a record that has not left its stage within a stage-defined number of days is flagged, searchable, and visually marked. The rotting widget is the kanban part of that marking. It renders a small rounded pill with the number of days the record has been stuck, such as 12d, and a tooltip explaining it, with wording that adapts to the model: leads get "This lead has been stuck in this stage for 12 days.", applicants and tasks get their own phrasing, and any other model gets a generic sentence.

The widget itself is deliberately dumb. It has no options, computes nothing, and renders nothing at all unless the record's is_rotting boolean is true. All the logic lives server side in mail.tracking.duration.mixin, which computes is_rotting and rotting_days from the record's last stage change and the stage's rotting_threshold_days value. A threshold of 0 on the stage disables rotting entirely, which is the default.

In core, the field element the widget sits on is rotting_days itself, placed inside the kanban card next to the salesperson avatar. CRM leads, recruitment applicants and project tasks are the three models wired up in Odoo 19.

What this means for your team

Every pipeline accumulates dead weight: the quote nobody chased, the applicant nobody called back, the task that quietly stalled. Before Odoo 19 you found those by sorting on a date column or building a filter, which means you found them when you went looking. Rotting inverts that: the pipeline itself tells you, on every kanban card, that something has been sitting too long.

The decision that matters is the threshold per stage, and it belongs to whoever owns the process. A five day threshold on a "Proposal sent" stage encodes the rule that no proposal goes a week without a follow up. Stages where waiting is normal, such as "Won" or a long legal review, should keep the threshold at 0 so the team does not learn to ignore red badges. In our implementations the fastest way to kill this feature is to set aggressive thresholds on every stage at once; within a month the whole board is red and nobody sees it anymore.

Because is_rotting is searchable, the same feature powers a stock Rotting filter in the CRM search panel, so a sales manager can open the pipeline, apply one filter, and run the Monday review from exactly the records that need rescuing.

Working examples

The core pattern, from the CRM pipeline kanban

<field name="is_rotting" invisible="1"/>
<field name="rotting_days" class="d-flex" widget="rotting"/>

The widget goes on rotting_days; is_rotting rides along invisibly as the display gate.

Enabling rotting on a custom model

The model needs the mixin and a tracked stage field, and the stage model needs the threshold:

class HelpdeskTicket(models.Model):
    _name = "my.ticket"
    _inherit = ["mail.tracking.duration.mixin"]
    _track_duration_field = "stage_id"

class TicketStage(models.Model):
    _name = "my.ticket.stage"

    rotting_threshold_days = fields.Integer("Days to rot", default=0)

The mixin also expects a date_last_stage_update datetime on the model; with those pieces in place, is_rotting and rotting_days exist and the three rotting widgets work.

Filtering rotting records

<filter string="Rotting" name="filter_rotting" domain="[('is_rotting', '=', True)]"/>

One feature, three widgets, one registry quirk

The mixin's docstring maps the three widgets to their views in one line: "'rotting' for kanbans, 'rotting_statusbar_duration' for forms, 'badge_rotting' for lists." That sentence is the official architecture of the feature, and it explains a behavior that otherwise looks like a bug: rotting is registered in the fields registry as kanban.rotting only. There is no unprefixed registration, so putting widget="rotting" in a list or form view resolves nothing and falls back to the field's default widget. Use badge_rotting and rotting_statusbar_duration there instead.

Which stages can rot is a per-stage decision, and the threshold help text carries a subtlety worth knowing: changing rotting_threshold_days does not retroactively recompute records whose stage was last updated before the change. A stage that just got a threshold will start flagging records as they age under the new rule, not instantly repaint history.

Models can also narrow when rotting applies by overriding _get_rotting_domain(). CRM does exactly that: leads only rot while their won_status is pending, so won and lost opportunities never show the badge no matter how long they sit.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. Registered as rotting_days on the development branch; the name rotting is reassigned to the list badge. See below.
Odoo 19.0VerifiedIntroduced in 19.0 as kanban.rotting, together with the mail.tracking.duration.mixin rotting fields.
Odoo 18.0Not availableThe rotting feature does not exist.
Odoo 17.0Not availableThe rotting feature does not exist.
Odoo 16.0Not availableThe rotting feature does not exist.

Upgrade note. The whole rotting feature is new in Odoo 19. Databases upgraded from 18 or earlier get the fields and widgets automatically through the mail module, but every stage arrives with the threshold at 0, so nothing rots until someone configures the stages.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. The changes below are read from the public development branch, which is unstable and can still move before release; we re-verify this page against the shipped version.

The widget is renamed. On master, the component is RottingDaysField and the registration changes from kanban.rotting to rotting_days, with no view prefix, so it can be used in any view type. Custom kanban archs using widget="rotting" will need to switch to widget="rotting_days" after the upgrade.

The old name is reassigned, not retired. The list widget currently registered as list.badge_rotting becomes plain rotting on master. The name you use today for the kanban pill will, in Odoo 20, resolve to the list-style badge component instead. This is the sharpest rename gotcha we have seen in the 20 cycle so far, and it is exactly the kind of change our Odoo migration service greps for.

Internals move to the new props system (useProps, record.resModel instead of record.model.config.resModel), which only affects code that patches the component.

Common problems and fixes

SymptomCause and fix
No badge appears on any cardEvery stage still has rotting_threshold_days at 0, which disables the feature. Set Days to rot on the stages where waiting is abnormal.
Badge missing although the record is rottingis_rotting or rotting_days is not loaded in the kanban view; the widget declares no dependencies and fails silently. Add both fields to the kanban arch, is_rotting can be invisible.
widget="rotting" does nothing in a list or form viewThe widget is registered as kanban.rotting only. Use badge_rotting in lists and rotting_statusbar_duration in forms.
Won opportunities never show the badgeCRM overrides _get_rotting_domain so only pending leads rot. This is designed behavior, not a misconfiguration.
Raised the threshold but old records still show as rottingRotting compares against the last stage update date; the change is not retroactive for records updated before it. Touch the stage (move out and back) or wait for the compute to catch up on the next stage change.

Rotting badge vs the alternatives

WidgetBest forKey difference
rottingFlagging stuck records directly on kanban cardsDay-count pill gated by is_rotting, kanban views only
badge_rottingThe same flag in list viewsRenders the stage name plus the day pill, registered for lists only
rotting_statusbar_durationThe same flag on the form statusbarFull statusbar with per-stage durations and a rotting tooltip
remaining_daysDeadline-driven coloringCounts toward an explicit date field instead of time spent in a stage

The three rotting widgets are one feature split by view type; pick by where the badge should appear. remaining_days solves the adjacent problem, coloring by an explicit deadline field rather than by time spent in a stage.

Frequently asked questions

What does rotting mean in Odoo 19?+
A record is rotting when it has stayed in its current stage longer than the stage's rotting_threshold_days value. Odoo computes is_rotting and rotting_days on models that use mail.tracking.duration.mixin, and ships kanban, list and form widgets to display the state. CRM leads, recruitment applicants and project tasks have it enabled in core.
How do I turn rotting on or off?+
Per stage: edit the stage and set Days to rot (rotting_threshold_days). 0 disables it for that stage, any positive number enables it. There is no global switch; leaving every stage at 0 is the off state.
Why is the rotting badge not showing on my kanban cards?+
Three usual causes, in order: the stage threshold is still 0; the view is missing is_rotting or rotting_days, both of which the widget reads without declaring dependencies; or the record simply is not past the threshold yet. The widget renders nothing at all when is_rotting is false.
Can I use the rotting widget in list or form views?+
No. It is registered as kanban.rotting, so it only resolves inside kanban views. Odoo ships badge_rotting for lists and rotting_statusbar_duration for form statusbars as the counterparts.
Does the rotting widget have any options?+
None. It accepts only the standard field props. Behavior is controlled by the stage threshold on the server and by which fields the view loads, not by widget options.
Can I add rotting to my own model?+
Yes. Inherit mail.tracking.duration.mixin, set _track_duration_field to your stage field, make sure the model has date_last_stage_update, and add an integer rotting_threshold_days on the stage model. The fields and all three widgets then work on your model.
Is the rotting widget changing in Odoo 20?+
The development branch renames the registration to rotting_days and drops the kanban prefix, while reassigning the name rotting to what is currently the list badge widget. Nothing is final until Odoo 20 ships in late September 2026, and we will re-verify this page against the release.

Want a pipeline that flags its own dead weight?

We configure stage rot thresholds that match how your team actually sells, extend the rotting mixin to custom models like helpdesk or maintenance pipelines, and keep the widgets working through the Odoo 20 rename. Rotting is an Odoo 19 feature, so if you are on 16 to 18 this is one more reason to plan the upgrade.

Talk to an Odoo pipeline expert

How this page was produced

Verified by reading rotting_widget.js, its QWeb template, and mail_tracking_duration_mixin.py on the Odoo 19.0 branch, and diffing the same files against the public development branch for the Odoo 20 section. The registry name, the two undeclared field reads, the per-model tooltip strings and the CRM won-status domain override were all confirmed in the source; core usage was checked in the crm, hr_recruitment and project view files. Spotted something we missed? Tell us and we will correct the page.