Skip to main content
iVentureTeam

dynamic_selection

A selection field always offers every value it declares, unless you render it with dynamic_selection, the small account-module widget that shows only the options another field on the record allows. Odoo's e-invoicing localizations run on it.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 12, 2026Updated August 12, 20265 min read
Technical namedynamic_selection
Field typesselection
Viewsform, list
Moduleaccount, so any database running Invoicing has it
Used in core19 occurrences across 6 modules, including l10n_gr_edi, l10n_ro_edi_stock_batch, l10n_ro_edi_stock, account_qr_code_emv, l10n_es_edi_verifactu, l10n_fr_pdp
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0
No-code setupNo. The widget and its companion field are wired in view and model code
Alternativesselection, selection_badge, radio

What the Dynamic Selection widget does

dynamic_selection renders a selection field whose dropdown is filtered by the record itself. The widget reads a second field, named in its available_field option, expecting a char value of comma separated selection keys, and shows only the declared selection values whose keys are in that list.

The mechanism is deliberately primitive and deliberately powerful: the allowed list is ordinary field data. Compute it on the server from any logic you like, the document type, the country, the partner's tax regime, and the dropdown follows, record by record, live as dependencies change.

Core keeps this widget on a short leash: it exists in the account module and every core usage is an e-invoicing localization narrowing legal code lists, Greek myDATA classification codes, Romanian e-Transport, Spanish Veri*Factu, French PDP, to the subset valid for the current document. That is the problem it was built for: government code lists where the full list is huge and the valid subset depends on context.

What this means for your team

Every compliance screen has the same failure mode: a dropdown of eighty legal codes of which seventy six are invalid for this document, and a user who picks a plausible wrong one. The rejection arrives days later from the tax platform, and the correction costs more than the invoice was worth.

Filtering at the point of entry is the cheapest fix that exists. The user cannot pick what the record does not allow; the error class disappears instead of being caught. This is why Odoo's own localization teams reach for this widget wherever a government publishes conditional code lists.

The pattern generalizes far beyond tax codes. Shipping service levels valid per carrier, contract clauses valid per contract type, quality checks valid per product family: anywhere you keep writing onchange warnings for invalid combinations, a computed allow list plus this widget removes the combination instead of warning about it.

Supported options in Odoo 19

Verified against dynamic_selection.js in the Odoo 19.0 account module. The widget spreads the standard selection descriptor and adds one option of its own; available_field is effectively required, its prop is declared non optional, and the widget has no fallback to showing everything.

OptionTypeWhat it does
available_fieldfield nameRequired. Names a char field on the record containing the allowed selection keys, comma separated. Only declared selection values whose key is in that list are offered; an empty list means an empty dropdown.
placeholder_fieldfield nameInherited from the base selection descriptor: reads the placeholder text from another char field on the record.

The allowed values are keys, not labels. The char field must contain the technical selection keys, comma separated with no decoration: a,b,c. A stored value that is not in the current allowed list does not crash the view; the source's optional chaining renders it as blank in readonly, which is easy to misread as data loss. The value is still in the database.

Working examples

The pattern, as documented in the source itself

The source file carries its own usage comment, worth reproducing almost verbatim. On the model:

the_available_field = fields.Char()  # comma separated allowed keys
the_selection_field = fields.Selection([...])

In the view:

<field name="the_available_field" column_invisible="1"/>
<field name="the_selection_field" widget="dynamic_selection"
       options="{'available_field': 'the_available_field'}"/>

The available field must be in the view (invisible is fine) so its value is loaded with the record.

Computing the allowed list

allowed_codes = fields.Char(compute="_compute_allowed_codes")

@api.depends("move_type")
def _compute_allowed_codes(self):
    for move in self:
        if move.move_type == "out_refund":
            move.allowed_codes = "credit_a,credit_b"
        else:
            move.allowed_codes = "invoice_a,invoice_b,invoice_c"

Change move_type and the dropdown reshapes immediately; no reload needed because the compute updates the record in the browser.

Why readonly can render blank

The widget overrides two getters from the base selection field, and the second explains its one surprising behavior.

options filters the declared selection through the allowed list: super.options.filter(x => availableOptions.includes(x[0])), where availableOptions is the char value split on commas (an empty or unset field yields an empty list, and an empty dropdown).

string, the readonly label, is where the base widget would crash: it looks up the current value in the filtered options and reads its label. When yesterday's stored value is outside today's allowed list, that lookup finds nothing, and the override's optional chaining turns the crash into an empty string. The comment above the override says exactly this: sometimes no options are available.

Two inherited behaviors are worth knowing. The base selection descriptor contributes placeholder_field, and its extractProps switches on autosave in kanban views, so a click on this widget in a kanban card writes the record immediately, same as the plain selection widget.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. No changes visible on the development branch; see below.
Odoo 19.0VerifiedVerified against the shipped account module source.
Odoo 18.0VerifiedThe 18.0 and 19.0 files are byte-identical. Verified directly.

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 and are not final until release.

No changes. The file is ported to the new Owl props system (the 18.0 and 19.0 files are byte-identical, and the development branch only swaps the props declaration style); the option, the filtering logic and the registration are untouched. We re-verify after release.

Common problems and fixes

SymptomCause and fix
The dropdown is emptyThe available_field is empty, not loaded in the view, or its keys do not match the selection keys exactly. Put the available field in the view (column_invisible works), and check the compute writes exact keys, comma separated, no spaces or brackets.
Readonly cell shows nothing although a value is storedThe stored value is no longer in the allowed list; the widget renders blank instead of crashing. Either accept it (the data is intact), extend the allowed list to include legacy values, or migrate old records to valid keys.
Options do not update after changing the driving fieldThe available field is not recomputed in the browser, usually a missing @api.depends trigger. Add the driving fields to the compute's depends so the allow list refreshes with the record.
TypeError mentioning available_fieldThe option is missing; the widget declares the prop as required and has no show-everything fallback. Always pass options="{'available_field': '...'}"; for an unfiltered dropdown use the plain selection widget.
Kanban clicks save the record immediatelyInherited base-selection behavior: autosave is enabled for kanban views in extractProps. Expected. Keep the field off kanban cards if immediate writes are unwanted.

Dynamic Selection widget vs the alternatives

WidgetBest forKey difference
dynamic_selectionLegal code lists and other per-record option subsetsDropdown contents driven by a comma separated allow list on the record
selectionSelections whose options never depend on the recordAlways offers every declared value
selection_badgeShort selections picked at a glanceRenders options as pill buttons, no per-record filtering
radioA handful of always-visible choicesAll options rendered as radio buttons, no filtering

The boundary: this widget filters a selection by record data. If the choices live in another model, use a many2one with a domain, which is the relational way to say the same thing. If the choices never vary, the plain selection widget is one less moving part.

Frequently asked questions

How do I show different selection options per record in Odoo?+
Render the field with widget="dynamic_selection" and point available_field at a char field holding the allowed keys, comma separated. Compute that field from whatever logic decides validity. The full selection must still be declared on the model.
Does dynamic_selection change what can be stored?+
No. It filters the UI only; server code and imports can still write any declared selection value. Add a Python constraint if invalid combinations must be blocked at the data level.
What happens to records whose stored value is no longer allowed?+
The value stays in the database untouched. The widget just cannot display it: readonly shows blank, and the dropdown will not offer it again. Handle legacy values in your compute or migrate them.
Why does core only use this in localization modules?+
Because that is its sweet spot: government code lists (Greek myDATA, Romanian e-Transport, Spanish Veri*Factu, French PDP) where validity depends on document context. The widget itself is generic and works on any selection field once account is installed.
Can I use it on a many2one field?+
The inherited descriptor nominally accepts many2one, but the filtering logic splits a char into string keys and compares them to option identifiers, which for many2one are numeric ids. Use a domain on the many2one instead; that is the supported mechanism.
Does it work in list views?+
Yes, this is how the Romanian and Greek localizations use it in move lists. The available field must be loaded in the list too, typically with column_invisible="1".

Fighting a government code list right now?

myDATA, Veri*Factu, e-Transport, PEPPOL: e-invoicing mandates all come down to sending the right code from the right subset. We build and localize Odoo compliance flows, computed allow lists, validation constraints and the EDI plumbing behind them, so rejections stop reaching your accountants.

Book a free consultation

How this page was produced

This page was verified by reading dynamic_selection.js in the Odoo 19.0 account module, including both getter overrides and the usage comment in the file, plus selection_field.js in web for the inherited descriptor. The same file was compared on the 18.0 branch (byte-identical) and on the development branch for the Odoo 20 section. Spotted an error or a version difference? Tell us and we will correct the page.