Skip to main content
iVentureTeam

x2many_buttons

The x2many buttons widget renders a relation as a short row of clickable record buttons. It powers the "this document might be a duplicate of" banner on invoices.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 16, 2026Updated August 16, 20265 min read
Technical namex2many_buttons
Field typesmany2many, one2many
Viewsform
Moduleaccount
Used in core11 occurrences across 4 modules, including account, l10n_pl_bank_verification, sale, purchase
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0
No-code setupNo. Placement and the records-shown cap are set in XML by a developer.
Alternativesactionable_errors, many2many_tags, one2many

What the x2many buttons widget does

Some relations are not data the user edits but evidence the user should look at. The canonical example is Odoo's duplicate detection on vendor bills: the system computes a set of suspiciously similar moves and needs to show them inside a warning banner, compactly, with one-click access to each. A one2many list is too heavy for that, and tags are not clickable in the way needed. The x2many_buttons widget renders each related record as a small button carrying its display name.

Only the first few records get a button, three by default, and the widget appends an overflow control that opens the complete set in a list view. Both the buttons and the overflow are navigation, not editing: this widget never adds or removes records from the relation.

What this means for your team

Duplicate vendor bills are one of the quietly expensive accounting failure modes: pay the same invoice twice and recovering the money costs far more than the original booking. Odoo's duplicate banner works because checking a suspect takes one click, right from the bill you are entering. That immediacy is this widget's whole job, and it is why the same pattern was picked up by sales and purchase for their own cross-references.

There is one behavior your accounting team must know, because the widget will not warn them: clicking any of these buttons discards unsaved changes on the current record before navigating. On an invoice that was just OCR-filled or half-corrected, that means edits are gone. Train the team to save first, or accept the widget's assumption that a record under duplicate suspicion should not be saved yet anyway.

Working examples

The duplicate warning, from the invoice form

<div class="alert alert-warning" role="alert"
     invisible="not duplicated_ref_ids or state != 'draft'">
    <span>This document might be a duplicate of</span>
    <field name="duplicated_ref_ids"
           widget="x2many_buttons"
           nb_records_shown="1"
           string="Duplicated Documents"
           context="{'name_as_amount_total': True}"/>
</div>

Core shows a single inline button here and pushes everything else behind the overflow. The context key makes each duplicate's button label show its total amount, which is what you actually compare duplicates by.

On your own model

<field name="related_claim_ids"
       widget="x2many_buttons"
       nb_records_shown="5"
       string="Related Claims"/>

The target model must implement action_open_business_doc, the method the widget calls to build the destination action. Accounting documents have it; a custom model needs a one-line implementation returning its own form action.

Discard first, ask questions later

Three source-level details define how this widget really behaves.

Discard is unconditional. Both click paths, single button and overflow, call record.discard() before navigating. The component was designed for warning banners on draft records, where pending edits are expendable. There is no option to disable it.

Single records route through action_open_business_doc. Instead of hardcoding a form action, the widget asks the target model for one. On accounting models this method resolves the true business document, so a button pointing at a payment's move opens the payment, not the raw journal entry.

The overflow is model-aware. Opening the full list builds a list-plus-form action over the relation's ids, and if the related model is account.move it injects list_view_ref: account.view_duplicated_moves_tree_js, a list view purpose-built for comparing duplicates side by side. Every other model falls back to its default list view.

The widget also declares relatedFields: display_name, which is why the buttons have labels without the view listing any subfields.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentThe development branch file is byte-identical to 19.0 at the time of writing. Re-verified after release.
Odoo 19.0VerifiedVerified against the shipped source, including the new nb_records_shown attribute.
Odoo 18.0Partial / changedWorks, but always shows three inline buttons: nb_records_shown does not exist.
Odoo 17.0Not availableThe widget does not exist in the 17.0 account module.
Odoo 16.0Not availableNot available.

Upgrade note for 18 to 19. Odoo 18 always rendered three inline buttons; the nb_records_shown attribute does not exist there and is silently ignored if backported XML carries it. The discard-then-open behavior is identical in both versions.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. Our comparison is against the public development branch, which remains unstable until feature freeze, and this page is re-verified after release.

The report for this widget is short: the development branch file is byte-identical to Odoo 19.0 at the time of writing. The attribute surface, the discard behavior and the account.move special case all carry into Odoo 20 unchanged.

Common problems and fixes

SymptomCause and fix
My unsaved edits vanished after clicking a record buttonBy design: the widget discards the current record before navigating, on both click paths. Save before clicking. There is no option to disable the discard.
Clicking a button raises a missing method errorThe related model does not implement action_open_business_doc. Implement the method on the target model, returning the action to open one record.
nb_records_shown seems to be ignoredIt was written inside options, or the database is Odoo 18 where the attribute does not exist. Set it as a plain attribute on the field tag, on Odoo 19 or later.
The overflow list shows generic columns instead of the duplicate-review layoutThe dedicated list view is injected only when the related model is account.move. For other models, define a list view and point at it via a list_view_ref context key on an explicit action.
Buttons show record names where amounts would be more usefulLabels are display_name, which for moves can be switched to totals by context. Pass context="{'name_as_amount_total': True}" as the invoice form does.

X2many buttons widget vs the alternatives

WidgetBest forKey difference
x2many_buttonsCompact, clickable evidence of related records inside warning bannersPure navigation: discards edits and opens records, never edits the relation
actionable_errorsMessages with fix buttons rather than record referencesRenders a computed JSON of alerts, sorted by severity
many2many_tagsRelations users actively add to and remove fromEditable pills with search and create; no navigation on click by default
one2manyChild records with columns worth reading inlineFull embedded list, editable, and much heavier in a banner

Reach for this widget only when the relation is evidence to inspect. The moment users need to add or remove entries, you want tags or a list, and the moment the message matters more than the records, you want actionable_errors.

Frequently asked questions

What does the x2many_buttons widget do in Odoo?+
It renders a one2many or many2many field as a short row of clickable buttons, one per record, with an overflow control that opens the full set in a list view. Odoo uses it for the duplicate invoice warning on vendor bills.
How do I control how many buttons are shown?+
Set the nb_records_shown attribute on the field tag, for example nb_records_shown="1". The default is 3. This is an attribute, not an options key, and it exists from Odoo 19 onward.
Why does clicking a button discard my changes?+
The source calls record.discard() before opening the target in both click paths. The widget was built for warning banners on draft documents where pending edits are expendable. Save first if your edits matter; there is no opt-out.
Can I use x2many_buttons outside accounting?+
Yes, it is registered globally once account is installed, and sale and purchase already use it. The one requirement is that the related model implements action_open_business_doc, which is what the widget calls to open a single record.
Does x2many_buttons change in Odoo 20?+
The development branch file is currently byte-identical to Odoo 19, so no changes are visible at the time of writing. That branch is unstable by definition, and we re-verify this page once Odoo 20 is released in late September 2026.

Duplicate payments still slipping through?

Odoo's duplicate banner is only as good as the detection rules behind it. We tune duplicate detection, build custom cross-reference checks on this widget, and tighten AP controls for teams processing serious invoice volume in Odoo.

Tighten your AP workflow

How this page was produced

This page was verified by reading addons/account/static/src/components/x2many_buttons/x2many_buttons.js on the Odoo 19.0 branch, diffing it against 18.0 (which lacks nb_records_shown) and the development branch (identical), and reading the invoice form XML that ships the duplicate warning. The discard-before-open behavior is stated in the click handlers themselves. Found an inaccuracy? Report it and we will fix the page.