Skip to main content
iVentureTeam

many2one_binary

New in Odoo 20. It turns a many2one pointing at an attachment into a file upload box, so users drop a file rather than picking a record they never think of as a record.

September 18, 2026Updated September 18, 20264 min read
Odoo 20 form showing a many2one to an attachment rendered as a file upload area with the uploaded file listed.
Technical namemany2one_binary
Field typesmany2one
Viewsform
Moduleweb, present in every Odoo 20 database
Used in core6 uses across Odoo 20 Community and Enterprise views, excluding tests. Does not exist in Odoo 19.
VersionsOdoo 20.0
No-code setupNo Studio entry, though its one option is properly declared.
Alternativesbinary, many2many_binary, many2one, image

What the many2one_binary widget does

Storing a file as an ir.attachment and pointing at it with a many2one is a normal Odoo pattern. Rendering that field as a many2one is not: users do not think of their contract PDF as a record to look up.

This widget closes that gap. It renders the field as a file picker, wrapping the single attachment in a list so it can reuse the existing x2many binary component:

get files() {
    const attachment = this.props.record.data[this.props.name];
    return attachment ? [attachment] : [];
}

Uploading writes the new attachment's id, name and mimetype onto the field. Removing sets it to false.

It declares relatedFields for name and mimetype, so the file name and type are available for display without a second query.

What this means for your team

This is the difference between an interface that matches how the data is modeled and one that matches how people think. The modeling is right: one file, stored once, linked by id. The old rendering was not.

Where it matters most is on records people fill in under time pressure, where being asked to search for an attachment record instead of dragging a file is the kind of friction that gets a process abandoned.

Supported options in Odoo 20

One declared option, plus a styling hook that is an attribute rather than an option and catches people out.

OptionTypeWhat it does
accepted_file_extensionsstringComma-separated extension list passed to the file picker, for example '.pdf,.docx'. A client-side filter that shapes what the browser offers rather than what the server accepts, so it is convenience rather than validation.(since Odoo 20.0)

accepted_file_extensions is a client-side hint that populates the file picker's filter. It shapes what the browser offers, not what the server accepts, so a determined user can still submit something else. Treat it as convenience, not validation.

Working examples

Restricting to documents:

<field name="contract_attachment_id" widget="many2one_binary"
       options="{'accepted_file_extensions': '.pdf,.doc,.docx'}"/>

Styling, which uses the class attribute:

<field name="contract_attachment_id" widget="many2one_binary"
       class="o_field_highlight"/>

This is ignored, and is the common mistake:

<!-- className comes from the class attribute, not from options -->
<field name="contract_attachment_id" widget="many2one_binary"
       options="{'class': 'o_field_highlight'}"/>

What happens when an upload fails

File uploads fail routinely: the file is too large, the server rejects the type, the connection drops mid-transfer. How a widget handles that decides whether a user loses their work.

This one treats a failure as information rather than an exception:

async onFileUploaded([file]) {
    if (!file) {
        return;
    }
    if (file.error) {
        return this.notification.add(file.error, {
            title: _t("Uploading error"),
            type: "danger",
        });
    }
    await this.props.record.update({ ... });
}

A rejected file produces a red notification carrying the server's own message and nothing else changes. The form stays open, the rest of the record is untouched, and the user can try again. No exception propagates, so nothing else on the page breaks.

Two smaller details worth knowing. isEmpty is hardcoded to () => false, so Odoo never treats the field as empty. The drop area renders even when nothing is attached, which is what you want for an upload target but means the field never collapses in a list.

And the class handling is unusual. Most widgets take styling through additionalClasses in the descriptor. This one reads it per-field from the XML attribute:

extractProps: ({ attrs, options }) => ({
    acceptedFileExtensions: options.accepted_file_extensions,
    className: attrs.class,
}),

One value from options, one from attrs, in the same three-line function. Putting class inside the options dictionary silently does nothing.

Version compatibility

VersionStatusNotes
Odoo 19.0Not availableDoes not exist. A many2one to an attachment renders as a record selector.
Odoo 20.0VerifiedIntroduced in Odoo 20. Verified against the shipped 20.0 source.

This widget does not exist before Odoo 20.

What is changing in Odoo 20

Introduced in Odoo 20. In Odoo 19 a many2one to an attachment rendered as an ordinary record selector, so a file-picker interface needed a custom widget.

The component is written against the Odoo 20 runtime throughout: useProps with the t type builder for props, and usePlugin(NotificationPlugin) rather than the service hook Odoo 19 would have used. Neither of those APIs exists in Odoo 19, so this widget could not be backported without rewriting it.

Common problems and fixes

SymptomCause and fix
A class set in options does nothingclassName is read from the class attribute, not from the options dictionary. Move it onto the field tag: class="your-class".
A user uploaded a file type you meant to blockaccepted_file_extensions filters the browser's picker only; it is not server-side validation. Validate the mimetype server-side if the restriction matters.
The upload area shows even with no file attachedisEmpty is hardcoded to false, so the field is never treated as empty. Working as designed. It is an upload target, so it has to be visible.
An upload failed with a red notification and nothing savedThe server rejected the file; the widget surfaces the message and stops rather than throwing. Read the notification text, which carries the server's own reason.

Many2one_binary widget vs the alternatives

WidgetBest forKey difference
many2one_binaryA single linked attachment users should upload, not look upFile picker over a many2one, with graceful upload failure
binaryA file stored directly on the recordNo attachment relation to manage
many2many_binarySeveral attachments on one recordMultiple files rather than one
many2oneA genuine record relationRecord selector rather than file picker
imageA stored imageImage preview and cropping rather than a file list

Use binary when the file is stored directly on the record rather than as a linked attachment; it is simpler and has no relation to manage. Use many2many_binary when a record needs several files. Use this one specifically for the single-linked-attachment pattern, which is worth keeping when the same file must be referenced from more than one place.

Frequently asked questions

How do I make an Odoo many2one to an attachment behave like a file upload?+
In Odoo 20, use widget="many2one_binary". It renders a file picker, writes the uploaded attachment's id, name and mimetype onto the field, and sets the field to false on removal. Odoo 19 has no equivalent.
How do I restrict which file types can be uploaded?+
Set options="{'accepted_file_extensions': '.pdf,.docx'}". It filters the browser's file picker rather than validating server-side, so enforce the restriction in Python if it matters.
Why is my class option ignored?+
Because className is read from the field's class attribute, not from options. The widget's extractProps takes one value from options and one from attrs.
What happens if an upload fails?+
The widget shows a red notification carrying the server's error message and makes no change to the record. Nothing throws, so the rest of the form keeps working and the user can retry.

Attachments your users cannot find?

Linking files through ir.attachment is the right model and the wrong interface until somebody fixes the field. We build document handling in Odoo so uploading, finding and replacing a file takes one action each.

Book a free consultation

How this page was produced

Verified by reading addons/web/static/src/views/fields/many2one_binary/many2one_binary_field.js on the 20.0 branch of a local clone of the official Odoo repository, with the upload handler, the files getter and extractProps quoted above, and the single declared option read from supportedOptions. The absence of an Odoo 19 equivalent was confirmed by a registry diff between the two branches. The usage count comes from scanning every non-test XML file in Community, Enterprise and odoo/addons. Corrections welcome via our contact page.