Skip to main content
iVentureTeam

one2many

Order lines, BoM components, task checklists: the one2many widget is the embedded table Odoo runs on, and its options can do more than the documentation admits.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 12, 2026Updated August 12, 20268 min read
Odoo 19 form view showing the one2many widget as an embedded editable list of child records with an Add a line control and a pager, the classic order lines layout.
Studio nameOne2Many
Technical nameone2many
Field typesone2many, many2many
Viewsform, list
Also registered asmany2many (same component), plus compact list.one2many and list.many2many cell variants
Moduleweb, present in every Odoo database
Used in core12 explicit occurrences across 10 modules, including product, website_slides, account_peppol, account, base_automation, calendar, plus every one2many field with no widget attribute
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0
No-code setupYes, via Odoo Studio (Enterprise)
Alternativesmany2many_tags, many2many, many2many_binary, handle

What the One2many list does

Every embedded table in Odoo, sales order lines, BoM components, invoice lines, journal items, is this widget. It is the default renderer for one2many fields, mounting a full list or kanban of the child records inside the parent form, with inline editing when the sub list allows it, an Add a line control, a pager past the page size, and dialogs for row editing when inline is off.

Under the hood it is the X2ManyField component, registered under both one2many and many2many. The field type flips the semantics: one2many children are created and deleted, many2many entries are linked and unlinked, with an Add control that opens a select dialog excluding already linked records. What renders inside comes from the sub views: the <list>, <kanban> or <form> declared inside the field element, or the target model's defaults when none are inlined.

What this means for your team

Line editing is where ERP usability is won. The same one2many can be a friction free grid your sales team flies through, or a dialog maze they resent, and the difference is configuration: inline editable sub list for high volume entry, dialog forms when each child carries too many fields for a row, kanban mode when children are visual.

The options matter commercially because they encode process rules at the exact spot users would break them. Conditional domains on create and delete mean lines can be added while a quote is draft but not after confirmation, without a developer writing a constraint, and without training users to not do things the UI happily offers. Most teams do not know the domain form exists, so they either leave lines wide open or lock them harder than the process needs. The middle ground is one option away.

Setting it up in Odoo Studio (no code)

Odoo Studio (Enterprise) handles the standard setups.

  1. Open the form in Studio.

  2. Drag a One2Many field onto the form. It requires an existing many2one pointing back from the child model, which Studio prompts you to pick. For a fresh child table, drag Lines instead, which creates the child model arrangement for you.

  3. Click the field and use Edit List View to shape the embedded columns, or Edit Form View to design the dialog that opens from Add a line.

  4. General properties, visibility, readonly, required, apply from the Properties panel as usual.

What Studio cannot do here

Studio shapes the sub views; it does not reach the widget's behavior rules.

Conditional create and delete, the domain valued options that gate line editing by parent state, are XML only.

Making rows open in dialogs versus edit inline is the sub list's editable attribute, and the interaction between that, open_form_view and readonly states goes beyond what Studio exposes.

Filtering which records a many2many Add dialog offers is the field's domain, plus context defaults for records created from it. When embedded line behavior is the difference between a smooth rollout and a support queue, that is standard scope for an Odoo customization engagement.

Supported options in Odoo 19

Verified against x2many_field.js and the shared useActiveActions helper in relational_utils.js, Odoo 19.0 web module. The widget declares no supportedOptions metadata at all; its real option handling lives in the helper, which reads exactly the keys below from the options dict.

OptionTypeWhat it does
createboolean or domainAllows adding lines. A domain value is evaluated against the parent record, enabling state dependent creation. ANDed with the sub view's own create attribute, and inert while the field is readonly.(default: true)
deleteboolean or domainAllows removing child records in one2many mode, with the same domain form and sub view ANDing as create.(default: true)
writeboolean or domainGates editing of existing rows: when it resolves false the embedded renderer goes read only even though the field itself is editable. The readonly gating behavior is 19.0; the key itself was read in 18 too.(default: true)
linkboolean or domainMany2many mode only: allows linking existing records through the Add dialog. Ignored on one2many fields.(default: true)
unlinkboolean or domainMany2many mode only: allows detaching records without deleting them. Ignored on one2many fields.(default: true)
createEditbooleanRead by the shared helper as a plain boolean gating the create and edit path on top of create. Undocumented, camelCase as written in the source, and unlike its siblings it takes no domain form.

The booleans are secretly domains. For create, delete, link, unlink and write, any non boolean value is parsed as a domain and evaluated against the parent record's context, so options="{'create': [('state', '=', 'draft')]}" gates line creation by parent state, live, as the record changes. Also remember the two layer rule: the sub view's own attributes are ANDed with these options, so a <list create="false"> wins even when the option allows creation.

Working examples

Classic editable lines

<field name="order_line_ids">
  <list editable="bottom">
    <field name="product_id"/>
    <field name="quantity"/>
    <field name="price_unit"/>
  </list>
</field>

Lines locked once the parent leaves draft

<field name="line_ids"
       options="{'create': [('state', '=', 'draft')], 'delete': [('state', '=', 'draft')]}">
  <list editable="bottom">...</list>
</field>

Read only rows without making the field readonly

<field name="line_ids" options="{'write': False, 'create': False, 'delete': False}">
  <list>...</list>
</field>

Editable rows with an expand to form icon

<list editable="bottom" open_form_view="1">...</list>

crudOptions: the six keys and the two layers

The option pipeline. extractProps forwards the whole options dict as crudOptions, and useActiveActions reads six keys from it: create, createEdit, delete, link, unlink, write. Each non null value that is not a boolean is compiled as a domain and tested against the parent's evaluation context on every relevant render. The same helper ANDs in the sub view's own active actions, which is why both layers must agree. Two consequences follow directly from the source: the edit key is overwritten by the component with the parent record's edition state, so putting edit in options does nothing, and many2one options like no_create are simply never read.

Write is a real option. The embedded renderer's readonly flag is props.readonly || !activeActions.write, so 'write': False, or a domain, renders rows read only while the Add and delete controls follow their own options. That gating arrived in 19; the 18 renderer ignored write for readonly purposes.

Row opening rules. Rows open in a dialog only when the sub list is not editable. Editable lists can add the expand icon back with the list attribute open_form_view="1". The expand to full form path saves the parent first, and when that save fails, you get the danger notification asking to save changes first, a message users report as a bug and the source shows is a guard.

Mode differences. In many2many mode, Add opens the select dialog with already linked ids excluded through an injected domain, and kanban delete means forget the link, while one2many kanban delete truly deletes the child.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. The development branch moves crud options to field attributes and renames the kanban compiler. Details below.
Odoo 19.0VerifiedVerified against the shipped source, including the domain valued options and write gating.
Odoo 18.0VerifiedSame option keys via the shared helper. write did not yet drive row readonly, and kanban's add button said only Add.

Upgrade note for 18 to 19. XML carries over, with three behavior shifts to know: the write option now drives row readonly state as described above, kanban's add button label became Add plus the field label instead of a bare Add, and expanding an unsaved new row to the full form got a proper save and resolve flow instead of a plain save. None require view changes, all affect what users see.

What is changing in Odoo 20

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

The crud options move out of options. On master, extractProps builds crudOptions from the field node's attributes, picking create, delete, link, unlink and write, and no longer passes the options dict at all. In other words, options="{'create': False}" stops working and <field create="false"> becomes the way, aligning x2many fields with the attribute style the rest of the view language uses. Views leaning on option based gating, including the domain valued form, need review during the upgrade; the attribute path also accepts expressions, but the migration is not automatic.

The kanban compiler becomes the card compiler. The embedded kanban mode compiles through CardCompiler, part of the kanban to card rename rolling through the branch, which matters to anyone who patched the embedded kanban rendering.

This is the single most migration sensitive widget in this batch; if your forms encode line rules in options, put them on the Odoo 20 upgrade checklist now.

Common problems and fixes

SymptomCause and fix
no_create or no_open in options has no effectThose are many2one options; this widget's helper reads only create, createEdit, delete, link, unlink and write. Use the x2many option names, or attributes on the sub list.
create is true but Add a line is missingThe sub list arch carries create="false", and the two layers are ANDed, or the field is readonly. Check both the options dict and the embedded list's attributes.
Rows open a dialog instead of editing inlineThe embedded list has no editable attribute, and non editable lists open records in dialogs by design. Add editable="bottom" or "top" to the sub list.
"Please save your changes first" when expanding a lineOpening the full form requires saving the parent, and that save failed or produced inconsistent new rows. Resolve the validation errors on the parent, then expand.
The field shows "3 records" instead of a tableThe field sits in a plain list view cell, where the compact list.one2many variant renders counts. Expected. Embedded tables belong on the form view.
options based create and delete rules stop working after a future upgradeThe Odoo 20 branch reads crud flags from field attributes and drops the options path. Move the rules to create, delete, link, unlink and write attributes during the migration.
Deleting from an embedded kanban removed the record from another list tooOne2many kanban delete truly deletes the child record, unlike many2many which only forgets the link. Expected semantics; use many2many when children have a life of their own.

One2many list vs the alternatives

WidgetBest forKey difference
one2manyChild records with real data columnsFull embedded list or kanban with inline editing and per action rules
many2many_tagsRelations where only the name mattersInline pills with search, no columns, no row editing
many2manyShared records linked, not ownedSame component in link and unlink mode with a select dialog
many2many_binaryFile childrenUpload cards instead of data rows
handleCompanion for reordering linesDrag grip column inside the embedded list, not a replacement

Reach for the full embedded list only when children carry real data columns. When users only pick and see names, tags are faster; when the children are attachments, use the binary widgets; and in plain list view cells, accept the count rendering rather than forcing tables into table cells.

Frequently asked questions

Do I need widget="one2many" on a one2many field?+
No, it is the default renderer, so a bare field element already uses it. The widget attribute matters only to switch to something else, like tags on a many2many, or in rare cases to force many2many semantics via widget="many2many".
How do I stop users adding or deleting lines conditionally?+
Use the options with domain values: options="{'create': [('state', '=', 'draft')], 'delete': [('state', '=', 'draft')]}" evaluates against the parent record live. Plain true and false work too. This domain form is read straight from the source's active actions helper and is absent from the official docs.
Why does options="{'no_create': True}" not work on my one2many?+
Because that option belongs to many2one autocompletes. The x2many helper reads create, createEdit, delete, link, unlink and write, nothing else, so the fix is 'create': False, or create="false" on the embedded list.
How do I make rows editable inline instead of opening dialogs?+
Set editable="bottom" or top on the embedded list arch. Non editable sub lists open rows in dialog forms by design, and editable ones can still offer the full form through open_form_view="1".
Can I make the lines read only but keep adding allowed?+
Yes: options="{'write': False}" renders existing rows read only while create keeps its own setting. The write flag driving row readonly is an Odoo 19 behavior, one more reason version matters when copying snippets.
What is the difference between one2many and many2many rendering?+
Same component, different verbs. One2many owns its children: Add creates records, delete destroys them. Many2many links shared records: Add opens a select dialog that excludes already linked ids, and removal just detaches. The field type decides, plus an explicit widget="many2many" override.
Why does my one2many show as a count in list views?+
In a plain list view cell, Odoo swaps in the compact list.one2many registration, which renders No records, 1 record or N records instead of mounting a table inside a table. The full widget appears on the form.
What changes for one2many in Odoo 20?+
The development branch reads the crud flags from field attributes, create, delete, link, unlink, write, and stops passing the options dict, so option based rules including domain values need migrating to attributes. The embedded kanban also compiles through the renamed card compiler. Final shape lands with the September 2026 release, and this page will be re-verified.

Line editing that fights your sales team?

Order lines, BoMs and worksheet tables are edited hundreds of times a day, so every extra dialog and every over-locked row is paid in payroll. We tune Odoo embedded lists, edit modes, per state rules, sub view design, and prepare them for the Odoo 20 attribute migration before it lands on you.

Book a free consultation

How this page was produced

Verified by reading x2many_field.js, list_x2many_field.js and the useActiveActions and related helpers in relational_utils.js from the Odoo 19.0 web module, which together define the crud option semantics, the domain evaluation and the two layer AND with sub view attributes. The file was diffed against 18.0 for the behavior shifts listed and against the public development branch, where the options to attributes move is visible directly in extractProps. Spotted a nuance we missed? Tell us and we will amend the page.