Skip to main content
iVentureTeam

sol_product_many2one

Every product you pick on an Odoo quotation goes through sol_product_many2one: the widget that decides whether you get a plain product, a variant configurator, a combo picker, or a grid.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 25, 2026Updated August 25, 20267 min read
Odoo 19 quotation form where the sol_product_many2one widget shows a configurable product on an order line with the Edit Configuration button visible.
Technical namesol_product_many2one
Field typesmany2one
Viewsform (embedded order line list)
Modulesale, extended by event, rental and matrix modules
Used in core2 occurrences in 1 module: the product and product template columns of the quotation's order line list
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0, Odoo 17.0, Odoo 16.0
No-code setupNo. It is wired into the sale views; Studio does not expose it as a choice
Alternativesproduct_label_section_and_note_field, many2one, product_label_section_and_note_field_o2m, so_line_field

What the sale product widget does

Picking a product on a quotation looks like filling a many2one, but what happens next depends on the product, and this widget is the dispatcher. After a selection it asks the server, via get_single_product_variant, what kind of template was chosen. A template with exactly one variant and no options resolves silently to that variant. A configurable template opens the product configurator for attributes, custom values and optional products. A combo product opens the combo configurator, and if every combo slot has a preselected item it skips the dialog and saves directly. With the matrix module installed, matrix-mode templates open the order grid instead.

The widget inherits from product_label_section_and_note_field, which is why the same cell also renders section and note rows and merges the product name with the editable description underneath. On top of that it layers sale-specific labeling: the translated product name is stripped from the description display so salespeople read the name once, in their language, and combo lines show the quantity glued to the name, as in Lunch Combo x 3.

Configured lines grow an Edit Configuration button that reopens the right dialog with current attribute values, custom inputs fetched back from the server when needed, and combo selections reconstructed from the linked lines.

What this means for your team

This widget is the front door of CPQ in Odoo, and its behavior decides how fast quotes go out and how clean they arrive.

The silent paths are the productivity feature. Single-variant products and fully preselected combos never show a dialog, so a rep quoting standard items keeps typing without interruptions. Dialogs appear exactly when a human decision is required. If your reps complain about popups on every line, the products are configured as configurable when they should not be, a catalog problem this widget faithfully exposes.

Optional products are a built-in upsell step. When a configurable product carries optional items, the configurator offers them and inserts accepted ones as their own lines directly beneath the main product. Teams that maintain optional product lists on their templates get attach-rate lift for free.

Locked orders stay navigable. Once an order is locked or the line list is readonly, the product cell turns into a link that opens the product form, so service and finance can still inspect what was sold. That is a deliberate design decision in the widget, not an accident.

When quoting flows need custom steps, price rule popups, engineering checks, bundle logic, the extension points are here: empty hooks like _onProductUpdate and the additional-props methods exist precisely for modules to override, which is how event tickets and rentals plug in. Extending them is core Odoo development for us.

Supported options in Odoo 19

The descriptor spreads productLabelSectionAndNoteField, which itself is built from the many2one description, so the practical option surface is the m2o set plus the label family's addition, verified against the 19.0 source chain. The widget adds no declared options of its own; its extras arrive as props (readonlyField) and field dependencies instead.

OptionTypeWhat it does
no_createbooleanInherited from many2one. Stops reps from creating products from the quote; the option sales admins most often add here.
no_openbooleanInherited from many2one. Suppresses the product link. Note the widget forces the link back on when the line is readonly, by design.
no_quick_createbooleanInherited from many2one. Removes inline creation only.
no_create_editbooleanInherited from many2one. Removes the Create and Edit dialog entry only.
search_thresholdnumberInherited from many2one. Delays catalog search until this many characters are typed; useful on large catalogs.
placeholder_fieldfield nameInherited from many2one. Dynamic placeholder sourced from another field on the line.
show_label_warningbooleanInherited from the label base widget. Highlights lines whose label lacks a product; the sale widget extends the same highlight to product-less normal lines regardless.(default: false)

The five fieldDependencies are new in 19 and matter for custom views. is_configurable_product, product_type, service_tracking, product_template_attribute_value_ids and translated_product_name are auto-loaded wherever the widget appears. On 17 and 18 the views had to load these flags themselves, so a custom 18 view copied to 19 may carry now-redundant invisible fields, harmless, but safe to clean up.

Working examples

How core uses it (quotation order lines, simplified)

<field name="order_line" widget="section_and_note_one2many">
    <list editable="bottom">
        <field name="product_template_id"
               widget="sol_product_many2one"/>
        <field name="product_id"
               widget="sol_product_many2one"
               optional="hide"/>
    </list>
</field>

Core mounts the widget on the template column by default and keeps the variant column hidden; the widget detects which of the two it sits on through the field's relation and adapts, template mode is what triggers get_single_product_variant.

Blocking product creation from the quote

<field name="product_template_id"
       widget="sol_product_many2one"
       options="{'no_create': True}"/>

All standard many2one options pass through the inherited descriptor, and no_create is the one sales admins actually want: reps pick from the catalog instead of inventing products mid-quote.

Highlighting lines without a product

<field name="product_template_id"
       widget="sol_product_many2one"
       options="{'show_label_warning': True}"/>

Inherited from the label base: warns on label-only lines. The sale widget additionally forces the warning color on any non-section, non-note, non-downpayment line whose product is missing.

Guards, parallel writes and combo bookkeeping

The internal-update guard is the widget's most copied trick. A useEffect watches the product id, but reacts only when the change was flagged as internal, set inside the m2o's update path just before the write. Changes arriving from server onchanges, from the configurator applying its result, or from record reloads do not re-trigger dialogs. Custom widgets that chain dialogs off field changes without such a guard loop; this is the reference implementation.

Applying a configuration is a parallel write. The save handler builds the main line's values, variant, quantity, no-variant attribute values, custom attribute values as o2m commands, and applies optional products by inserting new lines at the correct index, all with the unlocked update so multiple lines write in parallel. Discarding a fresh configurator deletes the just-created line instead of leaving a half-configured stub.

Combos keep their children linked. A combo line stores its selection in selected_combo_items as JSON, stamps new combo lines with a generated virtual_id, and re-sorts the order lines by sequence after saving so combo items sit under their parent. Switching a combo line to another product first clears the selection JSON, which is what removes the child lines.

Version history in four steps. 16: a plain Many2OneField subclass, class-registered, configurator logic already present. 17: descriptor style on the m2o. 18: rebased onto the label-and-section base, adding the merged product cell, plus the combo configurator with the combo product type. 19: field dependencies, translated-name handling and the parallel-write apply path. Each step changed the base class, so JavaScript patches against this widget rarely survive a major version, plan for it in an Odoo migration.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. Same widget; file moved and configurator logic extracted to a shared mixin on the development branch. Re-verified after launch.
Odoo 19.0VerifiedField dependencies, translated product name handling and the parallel apply path verified against the shipped source.
Odoo 18.0VerifiedRebased onto the product label and section base; combo configurator added. Views must load helper fields themselves.
Odoo 17.0Partial / changedPlain many2one subclass with variant configurator flows; no label merging, no combos.
Odoo 16.0Partial / changedOriginal class-registered version; configurator flows present, modern option handling absent.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. Findings below are from the public development branch, unstable until feature freeze, and re-verified here after release.

The widget survives intact; the file does not. Registration name, descriptor spread, extractProps and all five field dependencies are unchanged, but the source moves from sale/static/src/js/sale_product_field.js into its own sale_product_field/ directory, and the configurator glue, applyProduct and friends, is extracted into a shared saleProductMixin patched onto the class. Prop declarations migrate to the typed-props API.

Who feels it: anyone importing from the old path or patching methods that moved into the mixin. XML views need nothing. If your customization overrides _getAdditionalDialogProps, _prepareNewLineData or the product-update hooks, re-target them against the mixin after the release and re-test the configurator flows.

Common problems and fixes

SymptomCause and fix
The configurator opens for products that should not need itThe template is marked configurable or carries attribute lines with choices. Fix the product template; single-variant templates resolve silently by design.
Configurator reopens after saving itA custom override updates product_id outside the internal-update guard. Route programmatic product writes through record.update outside the widget, or replicate the isInternalUpdate flag.
Optional products land at the bottom instead of under the lineA customization re-sorts order_line or overrides _prepareNewLineData without preserving the insertion index. Keep the index math from the save handler when overriding.
Combo child lines survive a product changeThe selected_combo_items JSON was not cleared because the change bypassed the widget's update path. Change combo products through the UI or clear selected_combo_items in your code first.
Product not clickable on a confirmed orderThe line is still editable; the link only forces itself on when the line or order_line list is readonly. Expected. Lock the order to get the link, or open the product from its name in the catalog.
Missing flags errors in a custom 18-style view on 19Nothing missing anymore: 19 auto-loads the five dependencies, and stale invisible fields sometimes shadow them with different attributes. Remove manually added helper fields and let fieldDependencies load them.

Sale product widget vs the alternatives

WidgetBest forKey difference
sol_product_many2oneThe product cell on sale order linesChains the m2o into variant, combo and grid configurators with an internal-update guard and sale-specific labels
product_label_section_and_note_fieldProduct cells on invoices and documents without configuratorsSame label and section behavior, none of the configurator chaining
many2oneAny plain relation columnNo product intelligence at all; picking a configurable template just stores it
product_label_section_and_note_field_o2mThe grid around these cells on invoicesThe one2many renderer companion, not the cell widget
so_line_fieldReferencing sale lines from other appsDisplays sale order lines with sale-aware naming instead of products

These widgets form one family tree: the sale widget extends the label widget, which extends the many2one. Pick the deepest one whose behavior you need, and remember the o2m grid around the cell is a separate widget choice.

Frequently asked questions

When does sol_product_many2one open a configurator instead of just setting the product?+
After you pick a product template, the widget calls get_single_product_variant. One variant and no options: it writes the variant silently. Configurable attributes or optional products: the product configurator opens. Combo type: the combo configurator opens, unless every slot is preselected, in which case it saves directly. Matrix-mode templates open the grid when sale_product_matrix is installed.
Why does the quotation use product_template_id instead of product_id?+
Core mounts the widget on the template column and hides the variant column by default. Template mode is what enables the variant resolution flow; the widget detects which column it sits on via the field's relation and even shows the variant's name on the template column once resolved.
Which fields does the widget load automatically in Odoo 19?+
Five dependencies declared on the descriptor: is_configurable_product, product_type, service_tracking, product_template_attribute_value_ids and translated_product_name. On 17 and 18 views had to load such flags manually, so this is one less trap in custom views since 19.
How do optional products end up as separate lines?+
Accepting optional products in the configurator inserts a new order line per accepted product directly below the configured line, at a computed index, then applies each product's values in parallel. Declining them leaves only the main line.
Why can I still click the product on a locked sale order?+
The widget forces the m2o link on when the line is readonly or the order line list is readonly, precisely so locked orders remain auditable. It overrides the usual rule that readonly many2ones lose their link.
What breaks when Odoo 20 arrives?+
Views: nothing, the name and options are unchanged. JavaScript customizations: imports and patches, because the file moves into a directory and the configurator glue is extracted into a shared saleProductMixin. Re-target overrides after the September 2026 release; we re-verify this page then.
Can I stop salespeople from creating products from the order line?+
Yes, pass options="{'no_create': True}" on the field. The full many2one option set flows through the inherited descriptor, so the usual creation-blocking options behave exactly as on a plain many2one.

Quotes stalling in the configurator?

Variant setups, combo menus, optional product upsells and matrix grids all route through this one widget, and catalog configuration decides whether it helps or interrupts. We tune Odoo sales catalogs and extend the configurator flow for teams on 16 through 19.

Streamline my quoting flow

How this page was produced

Verified by reading sale_product_field.js in the Odoo 19.0 sale module end to end, the inherited product_label_section_and_note_field.js descriptor, and the order line views that mount the widget. Configurator, combo and locked-order behavior were exercised on a clean Odoo 19 database with demo data, where the screenshot was captured. Version rows come from diffing the file across 16.0, 17.0, 18.0 and the public development branch, including the file move and mixin extraction on the latter. Report corrections via our contact page.