Skip to main content
iVentureTeam

product_label_section_and_note_field_o2m

The one2many widget that renders invoice and bill lines in Odoo 19: sections, notes, merged product labels, and a column layout that reconfigures itself depending on document type.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 25, 2026Updated August 25, 20267 min read
Technical nameproduct_label_section_and_note_field_o2m
Field typesone2many
Viewsform (embedded list)
Moduleaccount, installed with Invoicing or Accounting
Used in core2 occurrences across 2 modules: invoice_line_ids on the invoice form (account) and order lines in purchase
VersionsOdoo 20.0, Odoo 19.0
No-code setupNo. Applied in view XML; Studio has no toggle for it
Alternativesproduct_label_section_and_note_field, one2many, section_and_note_text, sol_product_many2one

What the invoice lines widget does

The lines grid on an invoice looks like an ordinary embedded list, but it is doing three unusual things at once, and this widget is the assembly point for all of them.

First, it inherits the section-and-note machinery: rows whose display_type is line_section or line_note drop their cells and render as full-width bold headings or italic notes, which is how quotations and invoices get their document-like structure. Second, a mixin patched in from the product module merges the product and its description into one cell, the label logic that shows the product name with the editable description under it. Third, and unique to this widget, the renderer rewrites the column layout at render time depending on what kind of document the parent is.

That last part is the reason this subclass exists. Columns marked optional="conditional" in the XML, Product, Quantity and Unit on the invoice form, are resolved dynamically: on a vendor bill the Product column defaults to hidden, because bills are often line-item text captured from a PDF, and on a customer invoice all three default to hidden when the Sale app is not installed, on the theory that an accounting-only database invoices in prose, not SKUs. Users can still reveal the columns from the optional-columns dropdown; the widget only changes the default.

What this means for your team

This widget is why a vendor bill screen feels different from a customer invoice screen without anyone configuring anything. Odoo is making a workflow assumption on your behalf: bills get typed or OCRed as text lines, invoices carry products only when there is a sales pipeline feeding them. For most companies the defaults are right, and knowing they are just defaults, every hidden column is one click away in the list's optional-columns dropdown, saves teams from filing bugs about missing Product columns.

The section and note rows are worth actual process attention. Finance teams that structure long invoices with sections, phases, milestones, retainers, produce documents clients approve faster, and since the same renderer family drives quotations and purchase orders, the structure carries through the quote-to-invoice chain. If your invoices come out of a custom flow, making that flow emit proper line_section rows rather than fake text lines is a small change with visible payoff.

When the defaults are wrong for you, a services company that wants products on bills, or a distributor that needs quantity visible everywhere, the fix is a view inheritance changing optional="conditional" to optional="show", typical Odoo customization territory, minutes not days.

Supported options in Odoo 19

The widget adds no declared options of its own; its descriptor spreads sectionAndNoteFieldOne2Many, which spreads the standard x2many field. That chain is exactly what makes the interesting knobs invisible: the three section-family options below are read in extractProps but never declared in supportedOptions, so no options panel will ever show them. All rows verified against the 19.0 source chain.

OptionTypeWhat it does
subsectionsbooleanEnables the two-level section hierarchy: line_subsection rows render as semibold sub-headings under bold sections. Read in extractProps only; it appears in no options panel or documentation.(default: false)(since Odoo 19.0)
hide_compositionbooleanHides the composition details the section renderer can display under grouped lines. extractProps-only, undocumented.(default: false)(since Odoo 19.0)
hide_pricesbooleanSuppresses price columns on section rows so structural rows stay clean. extractProps-only, undocumented.(default: false)(since Odoo 19.0)
createboolean or domainInherited from the x2many field. Gates the Add a line action; a domain is evaluated against the parent record.
deleteboolean or domainInherited from the x2many field. Gates row deletion, boolean or parent-evaluated domain.
writeboolean or domainInherited from the x2many field. When it resolves false the embedded rows become read only; core relies on this family to lock posted documents.

The crud options accept domains, not just booleans. Because the x2many descriptor is inherited, create, delete, link, unlink and write can each be a domain evaluated against the parent record, the pattern core uses to lock line editing on posted documents. The optional="conditional" value, by contrast, is an attribute on the columns inside the sub-list, not an option on the field.

Working examples

How core uses it (invoice form, simplified)

<field name="invoice_line_ids"
       widget="product_label_section_and_note_field_o2m">
    <list editable="bottom">
        <field name="product_id" optional="conditional"/>
        <field name="name" widget="section_and_note_text"/>
        <field name="quantity" optional="conditional"/>
        <field name="product_uom_id" optional="conditional"/>
    </list>
</field>

The parent form must expose move_type and is_sale_installed; the renderer reads both from the eval context to resolve the conditional columns.

Using the hidden section options

<field name="invoice_line_ids"
       widget="product_label_section_and_note_field_o2m"
       options="{'subsections': True, 'hide_prices': True}"/>

subsections enables the two-level section hierarchy (line_subsection rows render semibold under their bold parents), and hide_prices suppresses price columns on section rows. Neither appears in any documentation; both are read straight from extractProps.

Aggregating custom columns on section rows

<field name="invoice_line_ids"
       widget="product_label_section_and_note_field_o2m"
       aggregated_fields="price_subtotal, margin"/>

aggregated_fields is an attribute, not an option: a comma-separated list of numeric columns the section rows should subtotal for the lines beneath them.

Inside the conditional-column and readonly logic

The conditional-column algorithm, verbatim from the source. For every column whose optional attribute equals conditional and whose name is product_id, quantity or product_uom_id (a hardcoded whitelist), the renderer starts from show, then applies two rules: if parent.move_type is a purchase type (in_invoice, in_refund, in_receipt) the product_id column flips to hide; if it is a sale type (out_invoice, out_refund, out_receipt) and parent.is_sale_installed is false, all three flip to hide. The result feeds the normal optional-columns mechanism, so user preferences stored per view still win afterward.

Readonly styling is deliberately relaxed. isCellReadonly is overridden for the product columns and the label column: even when the base renderer computes readonly, the cells only get the readonly classes when the parent document is posted, cancel or locked. The comment in the source is explicit that this exists to stop readonly styling from appearing on cells that are in fact still editable, a purely visual override with no security meaning.

The mixin does the label magic. ProductNameAndDescriptionListRendererMixin is patched onto the renderer prototype at module load. It is the same code that powers the sale order grid, which is why product cells behave identically on quotations and invoices: name on top, description folded beneath, with product_id and product_template_id as the recognized product columns.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. The widget survives but the optional=conditional column logic is deleted on the development branch. Re-verified after launch.
Odoo 19.0VerifiedIntroduced in Odoo 19; all behavior on this page verified against the shipped source.
Odoo 18.0Not availableThe widget does not exist. Invoice lines render through the section-and-note family without the o2m product-label subclass.
Odoo 17.0Not availableThe widget does not exist.
Odoo 16.0Not availableThe widget does not exist.

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 this page will be re-verified against the release.

The widget survives, its signature trick does not. On the development branch the registration, the descriptor spread and the readonly relaxation are all unchanged, but processAllColumn and the conditionalColumns whitelist are deleted outright. optional="conditional" therefore stops meaning anything to this renderer: bill and invoice column defaults will come from whatever the upgraded views declare, not from client-side document-type logic.

What to check in a migration: any view inheritance you built around optional="conditional", and any assumption that vendor bills hide the Product column by default. A one-line comment on the branch also confirms product_template_id stays a recognized product column for the sale and purchase grids. Our migration team tracks exactly this kind of silent behavioral change between versions.

Common problems and fixes

SymptomCause and fix
Product column missing on vendor billsThe renderer defaults optional=conditional product columns to hidden on in_invoice, in_refund and in_receipt documents. Reveal it from the optional-columns dropdown, or inherit the view and set optional="show".
Product, Quantity and Unit all missing on customer invoicesThe Sale app is not installed, so the renderer hides all three conditional columns on out_ documents. Install Sale, or override the optional attributes in a view inheritance.
Conditional columns crash or behave randomly on a custom modelThe logic reads parent.move_type and parent.is_sale_installed from the eval context; your parent model lacks them. Use optional="show"/"hide" instead of conditional outside account.move, or provide equivalent fields.
subsections or hide_prices seem to be ignoredTypo risk is high because no options panel validates them; they are extractProps-only keys. Check the exact snake_case spelling in options="{...}" and that the widget is the o2m variant, not the base label widget.
Section rows do not subtotal a custom amount columnaggregated_fields is an attribute on the field element, not an option. Set aggregated_fields="your_field" on the field tag.
Cells look editable on a posted invoiceReadonly styling on product and label cells is intentionally deferred until the parent is posted, cancel or locked. Expected before posting; the fields save-lock correctly regardless of styling.

Invoice lines widget vs the alternatives

WidgetBest forKey difference
product_label_section_and_note_field_o2mInvoice and bill lines mixing products, sections and notesAdds document-type-aware conditional columns and product-label cells on top of the section renderer
product_label_section_and_note_fieldThe product cell itself inside these gridsThe many2one companion widget for a single cell, not the one2many grid
one2manyPlain child grids without sections or product labelsStandard embedded list; display_type rows render as ordinary lines
section_and_note_textThe description column inside section-capable gridsCell-level widget that switches char and text rendering per row type
sol_product_many2oneSale order lines with configuratorsSale's product cell built on the same label base, adding variant and combo configurators

The decision between this widget and its parents is simple: use this one when lines carry products, use the section-and-note parent when they carry only text structure, and fall back to the plain grid when you need neither sections nor product labels.

Frequently asked questions

Why does my vendor bill hide the Product column but my invoice shows it?+
The renderer resolves columns marked optional="conditional" by document type: purchase documents default the Product column to hidden, sale documents show it as long as the Sale app is installed. It is a default, not a restriction; the optional-columns dropdown brings any of them back.
What does optional="conditional" mean in Odoo views?+
It is a special value of the standard optional attribute that only this widget's renderer understands, and only for product_id, quantity and product_uom_id. Every other renderer treats it as an unknown value. On the Odoo 20 development branch the handling is deleted, so treat it as a 19-only mechanism.
Which fields must the parent form provide for the widget to work?+
The conditional logic reads move_type and is_sale_installed from the parent record's eval context, and the readonly styling reads state and locked. On account.move all four ship in the core form; custom parents must supply equivalents or avoid conditional columns.
How do I turn on subsections in invoice lines?+
Pass options="{'subsections': True}" on the field. The option is real but undocumented: it is read in extractProps and never declared in supportedOptions, so it shows up in no properties panel. Rows with display_type = 'line_subsection' then render as second-level headings.
Can I control which columns section rows aggregate?+
Yes, with the aggregated_fields attribute on the field element, a comma-separated list of numeric column names. Note it is an XML attribute, not an entry in options, which is a common mix-up with this widget family.
Is product_label_section_and_note_field_o2m available before Odoo 19?+
No. It first appears in 19. Odoo 18 renders invoice lines through the section-and-note family directly, and the base label widget product_label_section_and_note_field covers the product cell from 17 onward.
What changes for this widget in Odoo 20?+
The development branch keeps the widget but deletes the conditional-column mechanism entirely, so column visibility defaults return to whatever the views declare. Nothing is final until the September 2026 release; we re-check this page against the shipped code.

Invoice lines that match how your business actually bills?

Column defaults, section structures, aggregated subtotals and locked-document behavior all live in this one widget family. We tune Odoo invoicing screens for finance teams on 16 through 19, without forking core views.

Fix my invoicing screens

How this page was produced

Verified by reading product_label_section_and_note_field_o2m.js, the parent section_and_note_fields_backend.js descriptor chain, and the ProductNameAndDescriptionListRendererMixin in the Odoo 19.0 source, then confirming both core usages in the account and purchase view XML. The conditional-column behavior was exercised on a clean Odoo 19 database with and without Sale installed, where the screenshot was captured. The Odoo 20 section is a direct diff of the same file against the public development branch. Corrections welcome via our contact page.