Skip to main content
iVentureTeam

badges_many2one

New capability in Odoo 20, not a rename. It turns a many2one into a row of clickable badges, can put a different icon on each one, and keeps working when the connection drops.

September 18, 2026Updated September 18, 20265 min read
Technical namebadges_many2one
Field typesmany2one
Viewsform, list
Moduleweb, present in every Odoo 20 database
Used in core32 uses across Odoo 20 Community and Enterprise views, excluding tests. Does not exist in Odoo 19.
VersionsOdoo 20.0
No-code setupNo Studio entry, though all three of its options are properly declared.
Alternativesbadges_selection, many2one, many2one_avatar, radio

What the badges_many2one widget does

A many2one normally renders as an autocomplete. badges_many2one renders it as a row of badges, one per candidate record, with the selected one highlighted. Picking is a single click.

It shares a BaseBadgesField with badges_selection, so the two look and behave identically. The difference is where the choices come from: a selection list there, a database query here.

That query is the part that makes this more than a styling choice. With no icon field configured it uses a plain name_search. With one, it switches to a search_read so it can fetch the icon alongside the name in the same round trip.

What this means for your team

This suits a many2one with a small, stable set of records that people choose from constantly: a delivery method, a service level, a payment term. Those are relations rather than selections because someone has to maintain the list, but they behave like selections to the user, and a dropdown is friction for them.

The icon support is what makes it worth configuring rather than just enabling. If your target model already carries an icon or a class per record, each badge can show it, which turns a row of similar words into something readable at a glance.

Supported options in Odoo 20

Three options, all properly declared, which makes this one of the better-documented new widgets in Odoo 20. Two of them work together.

OptionTypeWhat it does
badge_limitnumberDisplays a dropdown once the badge count is higher than this value. Worth setting on any relation that can grow, since the widget loads all candidates.(default: 0, meaning unlimited)(since Odoo 20.0)
related_icon_fieldfield name on the co-modelName of a field on the TARGET model holding an icon class, so each record shows its own icon. Setting it changes the loading strategy from name_search to search_read so the icon arrives with the name.(since Odoo 20.0)
default_iconstringFallback icon class used when a record's related_icon_field is empty. Also usable on its own to give every badge the same icon.(since Odoo 20.0)

related_icon_field names a field on the co-model, not on the record carrying the many2one. When it is set, the widget changes how it loads candidates, using search_read with that field included rather than name_search, so the icon is available before a badge is drawn. default_icon covers records where that field is empty.

Working examples

Basic badges:

<field name="carrier_id" widget="badges_many2one"/>

With a per-record icon read from the target model, and a fallback:

<field name="carrier_id" widget="badges_many2one"
       options="{'related_icon_field': 'icon_class',
                 'default_icon': 'fa-truck', 'badge_limit': 6}"/>

A domain on the field still applies, so you can narrow the candidate list the usual way:

<field name="carrier_id" widget="badges_many2one"
       domain="[('active', '=', True)]"/>

What happens when the connection drops

Most relational widgets assume the server is reachable. This one does not, and the handling is explicit:

catch (error) {
    if (error instanceof ConnectionLostError) {
        const currentVal = record.data[name];
        if (!currentVal) {
            return [];
        }
        return [[currentVal.id, currentVal.display_name]];
    }
    throw error;
}

When the candidate query fails because the connection is gone, the widget does not throw and does not render an empty field. It falls back to a single badge showing whatever the record already holds, so the form still displays the truth about the record even though no new choice can be made. Any other error is rethrown normally.

That is worth knowing on tablets, warehouse terminals and anywhere else connectivity is unreliable. The field degrades to read-only rather than to broken.

The second detail worth planning around is that this widget loads the candidate list eagerly. It queries the co-model when the field renders, rather than when a user opens a dropdown. On a model with a handful of records that is free. On one with thousands it is a query per form open, and the badges would wrap into a wall anyway. Use badge_limit to collapse past a threshold, and reach for the ordinary many2one once the list stops being short.

For genuinely long lists the widget offers a Search More dialog, wired through useSelectCreate with creation disabled, so a user can find a record that is not on screen but cannot create one from here.

Version compatibility

VersionStatusNotes
Odoo 19.0Not availableDoes not exist. Odoo 19 has no badge widget for many2one fields.
Odoo 20.0VerifiedIntroduced in Odoo 20. Verified against the shipped 20.0 source.

This widget does not exist before Odoo 20.

What is changing in Odoo 20

Introduced in Odoo 20. There is no Odoo 19 equivalent: this is new capability rather than a rename of something older.

It arrives alongside badges_selection, which does the same job for selection fields, and together they replace the single selection_badge widget that Odoo 19 had. That one handled only selection fields, so a many2one rendered as badges was not possible in Odoo 19 without a custom widget.

Common problems and fixes

SymptomCause and fix
Only one badge shows and nothing can be selectedThe candidate query failed on a lost connection, so the widget fell back to displaying just the current value. Expected offline behavior. It recovers when the connection returns.
Icons do not appearrelated_icon_field names a field that does not exist on the target model, or is empty on those records. Check the field exists on the co-model. Set default_icon as a fallback.
The form is slow to openThe widget loads every candidate record when the field renders, not on demand. Narrow with a domain, set badge_limit, or use the ordinary many2one for a large relation.
A user cannot clear the valueDeselection is derived from the field's required state rather than from an option. Make the field non-required if clearing should be possible.

Badges_many2one widget vs the alternatives

WidgetBest forKey difference
badges_many2oneA small, stable relation people pick from constantlyBadge picker for a many2one, with per-record icons and an offline fallback
badges_selectionThe same interface for a selection fieldChoices come from a selection list rather than a model
many2oneA relation with many recordsLoads on demand rather than eagerly
many2one_avatarRelations to peopleShows a photo rather than a badge and icon
radioA short relation as radio buttonsDifferent visual language, no icons

Use badges_selection when the values are a selection list rather than records. Use the plain many2one when the target model is large, because this widget loads its candidates eagerly and badges stop being readable past a couple of dozen. Use many2one_avatar when the records are people and a face is more recognizable than a label.

Frequently asked questions

Can I show an Odoo many2one field as badges?+
In Odoo 20, yes: use widget="badges_many2one". It is new in that version, so Odoo 19 has no equivalent and would need a custom widget.
How do I put a different icon on each badge?+
Set related_icon_field to the name of a field on the target model that holds an icon class, and default_icon as a fallback. Odoo then loads candidates with search_read so the icon arrives with the name.
What happens if the connection drops?+
The widget catches ConnectionLostError specifically and falls back to showing a single badge with the record's current value, rather than throwing or rendering empty. Other errors are rethrown.
Is badges_many2one suitable for a large relation?+
No. It loads every candidate when the field renders, so a model with thousands of records means a query on every form open, and the badges would be unreadable anyway. Use the ordinary many2one past a couple of dozen records.

Forms where every choice is two clicks too many?

Odoo 20 adds real badge pickers for relations, but they load eagerly and turn into a performance problem on the wrong field. We know which fields earn them and configure the rest properly.

Talk to an Odoo consultant

How this page was produced

Verified by reading addons/web/static/src/views/fields/badges_many2one/badges_many2one_field.js on the 20.0 branch of a local clone of the official Odoo repository. The connection-lost fallback is quoted verbatim from the useSpecialData callback, and the search strategy switch between search_read and name_search is read from the same block. All three options come from the declared supportedOptions, and every one of them is also present in extractProps, so there are no hidden options on this widget. The usage count comes from scanning every non-test XML file in Community, Enterprise and odoo/addons. Corrections welcome via our contact page.