Skip to main content
iVentureTeam

badge_rotting

The badge_rotting widget renders a stage column that carries a red 12d pill when the record has been sitting in that stage too long. It is the list-view member of Odoo 19's rotting family.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 24, 2026Updated August 24, 20266 min read
Technical namebadge_rotting
Field typesmany2one (stage field)
Viewslist
Also registered aslist.badge_rotting is the only registration
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. The stage threshold is no-code, but placing the widget needs XML
Alternativesrotting, rotting_statusbar_duration, many2one

What the badge_rotting widget does

Odoo 19's rotting feature flags records that have overstayed their pipeline stage, and each view type gets its own widget for the flag. badge_rotting is the list-view one. Applied to the stage column, it renders the normal many2one value and, when the record's is_rotting boolean is true, appends a small rounded pill with the day count, formatted as 15d.

Under the hood it is Many2OneFieldRotting, built with buildM2OFieldDescription on top of the standard many2one field. That construction matters: the widget inherits the entire many2one option set, and core immediately uses it, shipping the CRM lead list with options="{'no_open': True}" so clicking the stage does not navigate away from the list.

One deliberate omission is documented in the source itself: because the pill is appended to another field's value, the component sets no tooltip of its own, "to prevent title overlap" with the many2one's title attribute. The kanban sibling shows the explanatory sentence on hover; the list badge stays quiet.

What this means for your team

Pipeline reviews happen in list views more often than managers admit: sort by salesperson, scan, and ask questions. badge_rotting puts the neglect signal directly in the column people already scan. A stage cell that reads Proposition 15d answers the first review question, "how long has this been sitting there", without a click, a filter, or a computed date column.

The practical wiring is one decision per stage: how many days is too many. That number, rotting_threshold_days, lives on the stage record and doubles as the on-off switch, with 0 meaning never rot. Thresholds should encode your follow-up rules, not a blanket ambition; a stage where a week of waiting is normal gets a higher number or stays off, otherwise the badge becomes wallpaper.

Because the same computation also powers the stock Rotting search filter, the list view combination is strong: filter to rotting records, keep the badge column visible for the day counts, and you have a ready-made recovery worklist for the Monday pipeline meeting.

Supported options in Odoo 19

The widget declares no options of its own; everything below is inherited from the many2one descriptor it is built on, read from rotting_widget.js and many2one_field.js in the Odoo 19.0 source. The rotting behavior itself has no option surface.

OptionTypeWhat it does
no_openbooleanInherited from many2one. Disables navigating to the stage record when the cell is clicked in readonly. Core CRM ships the widget with this option on.
no_createbooleanInherited from many2one. Removes both creation routes from the autocomplete in editable lists.
no_quick_createbooleanInherited from many2one. Removes only the inline create entry; the create-and-edit dialog remains.
no_create_editbooleanInherited from many2one. Removes only the create-and-edit dialog entry; inline quick create remains.
search_thresholdnumberInherited from many2one, new in 19. Minimum characters typed before the autocomplete queries the server.(since Odoo 19.0)
placeholder_fieldfield nameInherited from many2one, new in 19. Char field on the record supplying a dynamic placeholder for the empty cell.(since Odoo 19.0)

The pill needs two extra columns loaded. The component reads is_rotting and rotting_days straight off the record without declaring field dependencies. Core list views add both with column_invisible="True". Drop them from a custom list and the widget degrades to a plain many2one with no badge and no error.

Working examples

The core pattern, from the CRM lead list

<field name="stage_id" optional="show" widget="badge_rotting" options="{'no_open': True}"/>
<!-- further down the same list -->
<field name="is_rotting" column_invisible="True"/>
<field name="rotting_days" column_invisible="True"/>

Locking the column down further

<field name="stage_id" widget="badge_rotting"
       options="{'no_open': True, 'no_create': True}"/>

Both options come straight from the inherited many2one behavior: no navigation on click, no creating stages from the editable list cell.

A real many2one wearing a badge

Building the widget on the many2one descriptor rather than as a decoration has consequences worth knowing.

It is fully editable. In an editable list, the cell behaves exactly like a stage many2one: autocomplete, quick create, Search More, all subject to the usual options. The pill is cosmetic; editing writes to the stage field normally, and moving the record to a new stage resets the rot clock server side through date_last_stage_update.

The registration is list-prefixed. list.badge_rotting means the fields registry resolves it only inside list views. In a form or kanban arch, widget="badge_rotting" silently falls back to the default many2one. The mixin's docstring assigns the other views their own widgets: rotting for kanbans, rotting_statusbar_duration for forms.

The day count is a separate field from the one you place it on. You put the widget on stage_id, but the number in the pill comes from rotting_days, computed by mail.tracking.duration.mixin against the stage's rotting_threshold_days. CRM narrows it further: only leads whose won_status is pending can rot, so won and lost rows never badge.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. Registered as plain rotting on the development branch, while the kanban widget takes rotting_days. See below.
Odoo 19.0VerifiedIntroduced in 19.0 as list.badge_rotting, part of the rotting family.
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 family is new in Odoo 19. After upgrading, stages arrive with the threshold at 0, so lists stay badge-free until stages are configured.

What is changing in Odoo 20

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

The widget is renamed to rotting, dropping the list prefix, so it becomes usable in any view type. The rename collides head-on with today's naming: in Odoo 19, rotting is the kanban day-pill widget; on master that kanban widget becomes rotting_days and the name rotting now means this list badge component. Any custom arch using either name needs a review at upgrade time, and a find-and-replace done in the wrong order will swap the two behaviors. This is precisely the sort of silent semantic change our migration service audits for.

Internals follow the new props system (useProps, Record type imports), relevant only to code patching the component.

Common problems and fixes

SymptomCause and fix
Stage shows but the day pill never appearsis_rotting or rotting_days is not loaded in the list; the widget reads both without declaring dependencies. Add both fields with column_invisible="True", as core does.
Widget does nothing in a form or kanban viewRegistered as list.badge_rotting, lists only. Use rotting_statusbar_duration on forms and rotting on kanbans.
No row in the whole database shows a pillAll stages have rotting_threshold_days at 0, the shipped default. Set Days to rot on the stages that should alert.
Clicking the stage opens the stage record and loses the listThe inherited many2one still navigates by default. Add options="{'no_open': True}" like the core views.
Won or lost leads never show as rottingCRM's _get_rotting_domain restricts rotting to pending leads. Designed behavior; override the domain in a custom module if you truly need it.

Badge_rotting widget vs the alternatives

WidgetBest forKey difference
badge_rottingStage columns that should flag stale rowsFull many2one plus a conditional day pill, list views only
rottingThe same flag on kanban cardsDay pill only, no stage editing, kanban registration
rotting_statusbar_durationThe same flag on the form statusbarStatusbar with per-stage durations and a tooltip
many2oneStage columns without staleness logicSame editing behavior, no pill, no extra field requirements

Within the rotting family, choose by view type. If what you want in a list is a colored stage chip without the staleness logic, the badge and status widgets below do that with less machinery.

Frequently asked questions

What does the badge_rotting widget do?+
It renders a many2one stage column normally and appends a rounded pill with the number of days the record has been stuck, such as 15d, once the record's is_rotting flag is true. It ships in Odoo 19 on the CRM lead, recruitment applicant and project task list views.
Which options does badge_rotting accept?+
All standard many2one options, because it is built with buildM2OFieldDescription on the many2one field. Core uses no_open; no_create, search_threshold and the rest work the same way. The rotting display itself has no options.
Why is the pill not showing in my custom list view?+
The component reads is_rotting and rotting_days from the record but declares no field dependencies, so you must load both in the list yourself, typically with column_invisible="True". Without them the widget behaves like a plain many2one.
Can I use badge_rotting in a form view?+
No, the registration is list.badge_rotting, which only resolves in list views. Forms use rotting_statusbar_duration on the stage statusbar instead, and kanbans use rotting.
Where does the day count come from?+
From rotting_days, an integer computed by mail.tracking.duration.mixin from the record's last stage change, compared to the stage's rotting_threshold_days. The widget displays it; it never computes anything client side.
Is badge_rotting affected by the Odoo 20 changes?+
Yes, directly: the development branch renames it to plain rotting, while the current kanban widget of that name becomes rotting_days. Until Odoo 20 ships in late September 2026 this can still change, and we will re-verify the page against the release.

List views that surface neglected deals on their own

We wire the rotting fields into your custom list views, tune per-stage thresholds with your sales ops team, and audit every widget rename before an Odoo 20 upgrade so a stage column never silently changes behavior. If your pipeline reviews still start with an export to Excel, that is fixable.

Get a pipeline view audit

How this page was produced

Verified by reading rotting_widget.js (component, template and registration), many2one_field.js for the inherited option set, and mail_tracking_duration_mixin.py for the server side, all on the Odoo 19.0 branch, then diffed against the public development branch for the Odoo 20 section. Core usage including the no_open option was read from the crm, hr_recruitment and project list archs. Corrections are welcome via our contact page.