Skip to main content
iVentureTeam

skills_one2many

The skills block on an Odoo employee form is not a normal one2many list. skills_one2many ships its own renderer with hardcoded grouping, permission checks, and a report shortcut.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 21, 2026Updated August 21, 20266 min read
Technical nameskills_one2many
Field typesone2many, many2many
Viewsform
Modulehr_skills
Used in core3 occurrences across 2 modules: employee forms in hr_skills and applicant forms in hr_recruitment_skills
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0, Odoo 17.0, Odoo 16.0
No-code setupNo. Applied via the widget attribute in view XML
Alternativesone2many, resume_one2many, many2many_tags, section_one2many

What the Skills list does

Install Odoo's Skills Management and every employee form gains a skills block: languages, technical skills, and certifications, each with a level and a progress value, grouped under their skill type. That block is one field, the employee_skill_ids one2many, rendered by this widget.

Structurally it is the standard x2many field component with two swaps. The list renderer is replaced by a SkillsListRenderer that inherits from a shared CommonSkillsListRenderer (the same family the resume block uses) and adds skills-specific behavior: fixed grouping by skill type, a check for whether any skill types exist yet, and a permission-gated timeline with a jump into the skills history report. The record dialog is replaced by a customized open flow that titles the wizard "Update Skills" and injects the right default employee.

Because the descriptor spreads the generic x2ManyField, everything else, crud gating, sub-view resolution, pagers, behaves exactly as on a normal one2many.

What this means for your team

A skills matrix is only as useful as it is current, and this widget's design quietly pushes in that direction. The Update Skills dialog edits levels in place rather than making users manage rows in a grid, which is the difference between HR updating fifty employees in an afternoon or not doing it at all.

The permission logic matters for rollouts. The timeline and the Skills Report shortcut appear only to HR users and to managers viewing their own subordinates, computed at load from the HR access group and the employee hierarchy. Line employees see their skills without the historical analytics. If a manager reports "the report button is missing", the first check is whether the employee sits in their subordinate tree, not a bug hunt.

For recruitment, the same widget renders applicant skills, with the timeline suppressed through a context key since applicants have no history worth charting. That reuse is the point of the shared renderer family: one skills UI across employees, applicants, and resumes, so training carries over.

Supported options in Odoo 19

Verified against skills_one2many.js in the Odoo 19.0 hr_skills module. The widget declares no options of its own. It inherits the x2many crud options below, which the generic component reads from the options dict at runtime without ever declaring them, and it honors one context key that is easy to mistake for an option.

OptionTypeWhat it does
createboolean or domainInherited x2many crud gate for adding skills. Read from the options dict at runtime; never declared in supportedOptions. Moves to a view attribute on the Odoo 20 development branch.
deleteboolean or domainInherited x2many crud gate for removing skill lines. Same runtime-read, undeclared status as create.
linkboolean or domainInherited x2many gate controlling the Add button on many2many usage; falls back to create when absent.
unlinkboolean or domainInherited x2many gate for detaching rather than deleting on many2many usage.
writeboolean or domainInherited x2many gate; when false the sub-list renders readonly. The readonly gating itself is 19-new in the generic component.

no_timeline goes in context, not options. The renderer reads this.props.list.context.no_timeline, so writing it into options="{...}" does nothing. Core's applicant view passes it via the field's context attribute.

Working examples

As core uses it on the employee form

<field name="employee_skill_ids" widget="skills_one2many"/>

Applicant skills without the timeline

<field name="applicant_skill_ids" widget="skills_one2many"
       context="{'no_timeline': True}"/>

The context key, not an option, suppresses the timeline block.

Restricting what users can do

<field name="employee_skill_ids" widget="skills_one2many"
       options="{'delete': False}"/>

The inherited x2many crud options apply normally, so removing deletion or creation works exactly as on a plain one2many.

Permissions, grouping, and the 18 to 19 save change

Four behaviors come straight from the renderer's onWillStart and getters.

The permission model is client-computed. At load the renderer reads the current user's employee_ids, checks hr.group_hr_user, and runs a child_of search over hr.employee to build the user's subordinate list. The timeline getter then shows extras only when the viewer is an HR user or the form's employee is in that subordinate set. This is display gating, not security: the real protection is the model's access rules, and the widget just avoids dangling links.

Grouping cannot be configured. get groupBy() { return 'skill_type_id'; }. Custom grouping means subclassing the renderer.

The employee default handles the res.users case. When the parent record is a user rather than an employee, the Add flow resolves record.data.employee_id.id instead of the record id before injecting default_employee_id, which is why the same widget works on the user preferences form.

Odoo 18 saved harder than 19 does. In 18 the widget's save hook saved the sub-record and then immediately saved the parent record too, rolling the row back if that failed. Odoo 19 dropped the parent save, so skills edits commit with the form like a normal one2many, a real behavior change to retest after migration. The 18 dialog was also titled "Select Skills" and the report action pointed at the old hr.employee.skill.log model; 19 renamed the dialog "Update Skills" and moved the report to hr.employee.skill.history.report.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. Widget file identical on the development branch, but inherited crud options move to attributes upstream; details below.
Odoo 19.0VerifiedVerified against the shipped source. Update Skills dialog, no parent autosave, new history report model.
Odoo 18.0Partial / changedSame widget, but the dialog is titled Select Skills, each skill save also saves the parent record, and the report uses hr.employee.skill.log.
Odoo 17.0VerifiedPresent at the current file path with the same renderer architecture.
Odoo 16.0Partial / changedPresent under the older hr_skills file layout with the same registration name and a simpler renderer.

Upgrade note for 18 to 19. Skills stopped saving the parent record on every skill edit, the wizard title changed, and the history report moved to a new model. If you patched hr.employee.skill.log reports or relied on the immediate parent save, both need attention during an Odoo migration.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. These notes come from the public development branch, which is unstable until feature freeze; we re-verify against the shipped release.

The widget's file is byte-identical on master. The moving part is upstream: the generic x2many component on the development branch stops reading crud permissions from the options dict and takes them from view attributes instead (create, delete, link, unlink, write picked from attrs). If that lands, an options="{'delete': False}" on your skills field would need rewriting as an attribute. The kanban-to-card renderer rename also lands in the same area.

Common problems and fixes

SymptomCause and fix
Timeline and Skills Report missing for a managerThe extras show only to HR users or managers whose subordinate tree contains the employee. Check the employee's manager chain or grant hr.group_hr_user.
no_timeline in options has no effectThe renderer reads it from the field's context, not from options. Pass context="{'no_timeline': True}" on the field.
Skills block shows a prompt about skill types instead of skillsNo hr.skill.type records exist yet; the renderer checks the count at load. Configure skill types first, via the shortcut the widget offers or the Skills settings.
Skills edits no longer save instantly after upgrading to 19Odoo 19 removed the automatic parent-record save that 18 performed after each skill change. Save the form as with any one2many; retrain users if needed.
Cannot group skills by something other than typeGrouping is a hardcoded getter returning skill_type_id. Subclass SkillsListRenderer in a custom module to override groupBy.

Skills list vs the alternatives

WidgetBest forKey difference
skills_one2manyEmployee and applicant skills with levels and progressPurpose-built renderer: fixed grouping, gated timeline, tuned dialog
one2manyGeneric child-record gridsPlain rows, configurable columns, no skills logic
resume_one2manyThe employee resume timeline blockSame renderer family tuned for resume lines
many2many_tagsFlat tagging without levelsPills with no level, progress, or grouping
section_one2manySectioned lists like planning slotsSections come from a display_type row, not a related field

This widget is the reference pattern for a themed x2many: keep the generic component, swap the renderer, harden the dialog. If you need a custom grouped child list anywhere in Odoo, reading this source first will save a day.

Frequently asked questions

What does skills_one2many add over a plain one2many?+
A custom renderer that always groups by skill type, checks whether skill types exist, shows a permission-gated timeline and Skills Report link, and an Add flow retitled Update Skills that injects the right default employee, including when the parent record is a res.users.
Who sees the skills timeline and report button?+
HR users (hr.group_hr_user) and managers viewing employees inside their subordinate tree, which the renderer computes with a child_of search at load. Everyone else sees the plain skills list. It is display gating; model access rules remain the actual security.
How do I hide the timeline?+
Pass context="{'no_timeline': True}" on the field, as the recruitment module does for applicants. It is a context key; putting it in options does nothing.
Can I group skills by level or by certification instead of type?+
Not by configuration. The renderer hardcodes skill_type_id in a getter, so custom grouping means extending SkillsListRenderer in a small module and overriding groupBy.
Why did skills behave differently after upgrading from Odoo 18?+
18 saved the whole employee record after every skill change and titled the dialog Select Skills; 19 dropped the parent save, renamed the dialog Update Skills, and moved the history report to hr.employee.skill.history.report. All three are visible in the source diff.
Does the widget work on many2many fields?+
Yes. The descriptor inherits the generic x2many component, which supports both one2many and many2many; the open-record flow even adjusts its parent-id handling based on the widget name. Core only uses it on one2many skills fields.

Skills data that HR actually maintains?

We implement Odoo Skills Management end to end: skill taxonomies, levels, recruitment matching, and the custom renderers when the stock matrix is not enough. From HR module setup to bespoke widgets, on Odoo 16 through 19.

Book a free consultation

How this page was produced

This page was verified by reading skills_one2many.js and the shared skills_list_renderer.js on the Odoo 19.0 branch, diffing against 16.0 (old file layout), 17.0, 18.0, and master, and confirming core usage in the hr_skills and hr_recruitment_skills views. The 18-to-19 save behavior change is taken from the two branches' sources side by side. The screenshot was captured on a clean Odoo 19 database with Skills Management installed. Corrections welcome via our contact page.