Skip to main content
iVentureTeam

x2_many_image

The image tile in Odoo's product media kanban is not the plain image widget. x2_many_image swaps the file picker for the media dialog, and quietly disables three inherited options while doing it.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 27, 2026Updated August 27, 20266 min read
Technical namex2_many_image
Field typesbinary, many2one (inherited from the image widget)
Viewskanban, list, form, always inside an x2many subview
Modulehtml_editor, installed with Website, Sales and any app that ships the editor
Used in core1 occurrence, the product media kanban in website_sale
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0
No-code setupNo. This widget is set in view XML and only behaves correctly inside an x2many subview
Alternativesimage, x2_many_media_viewer, video_preview, many2many_binary

What the x2_many_image field does

A product in Odoo can carry a gallery: extra photos, and video links that play on the shop page. Those extra media items are child records of the product, and each one shows up as a tile in a kanban subview. x2_many_image is the widget that draws that tile.

It is a thin subclass of the standard image widget. The picture, the sizing options and the zoom behavior are all inherited. What changes is the editing path. Instead of opening your computer's file picker, the pencil button opens Odoo's media dialog, the same one the website editor uses, so an image can be picked from what is already uploaded, and a video can be pasted as a link rather than uploaded as a file.

The second change is the trash button. On the plain image widget it clears the picture and leaves the record. Here it deletes the child record from the parent relation entirely, which is why the widget belongs inside an x2many subview and nowhere else.

What this means for your team

The commercial point is the video path. Product galleries that mix photos with a YouTube or Vimeo clip convert better than photos alone, and this widget is what lets a catalog manager add that clip without leaving the product form and without asking anyone to upload a video file. The URL is stored, the platform is recognized, and the shop template does the embedding.

The operational point is reuse. Because the media dialog lists attachments that already exist in the database, the same lifestyle shot can be attached to twenty products without twenty uploads. That keeps the database smaller and keeps a product catalog visually consistent, which matters more than it sounds when several people maintain the same catalog.

The trap to brief your team on is the trash button. On a media tile it does not blank the image, it removes the media record. Users who expect the familiar clear behavior lose the whole gallery entry, including its video URL and its position in the sequence.

Supported options in Odoo 19

The widget declares no options of its own. Its descriptor spreads the image widget's descriptor, so the option list below is inherited. What matters is which of them survive the replaced template, and the table says so explicitly. All of it read from x2many_image_field.js and image_field.js in the Odoo 19.0 source.

OptionTypeWhat it does
sizelistTwo-element list controlling the rendered width and height, for example [0, 180]. Works normally, because it acts on the image element the widget keeps.
img_classstringCSS classes applied to the image element. Undocumented on the base image widget too: read in extractProps but never declared in supportedOptions. Works here.
preview_imagefieldRenders a smaller companion binary field instead of the main one, useful when the full-size field is heavy. Works here.
zoombooleanShows the enlarged image in a tooltip on hover. Works here.
zoom_delaynumberMilliseconds before the zoom tooltip appears. Only meaningful together with zoom. Works here.
reloadbooleanAdds the record's write date to the image URL so the browser refetches after a change. Works here.(default: true)
accepted_file_extensionsstring<strong>Inert in this widget.</strong> It only configures the file uploader, and the widget's template replaces the block that contains it.
convert_to_webpboolean<strong>Inert in this widget.</strong> It is only read inside the uploader's callback, which the replaced template makes unreachable.

Two inherited options are dead here. The widget's template replaces the block that holds the file uploader, and both accepted_file_extensions and convert_to_webp are only consumed by that uploader. Set them and nothing happens, with no warning. If you need WebP conversion on product media, it has to come from the media dialog side or from a server-side rule, not from this widget.

Working examples

The core product media kanban

<field name="product_template_image_ids" mode="kanban">
  <kanban>
    <field name="video_url" invisible="1"/>
    <field name="sequence" widget="handle"/>
    <templates>
      <t t-name="card">
        <field name="image_1920" widget="x2_many_image"/>
      </t>
    </templates>
  </kanban>
</field>

This is the shape core uses. Note video_url loaded invisibly: the widget writes to it by name, so it has to be in the subview.

Sizing the tile

<field name="image_1920" widget="x2_many_image"
       options="{'size': [0, 180], 'img_class': 'w-100'}"/>

size and the undocumented img_class both survive, because they act on the image element the widget does not replace.

A media model that works with it

<!-- the child model needs these field names -->
<field name="name"/>          <!-- overwritten on image save -->
<field name="video_url"/>     <!-- written on video save -->
<field name="image_1920" widget="x2_many_image"/>

Both names are hardcoded in the widget. A custom media model that calls them title and url will render fine and then fail silently on save.

What the replaced template silently disables

The most useful thing in this file is what the template removes. html_editor.ImageField inherits web.ImageField in primary mode and replaces the first inner div, which in the base template is the container holding the FileUploader component and both control buttons. Three consequences follow, and none of them is documented.

First, there is no file uploader left, so the two options that feed it are inert: accepted_file_extensions, which sets the accept attribute on the hidden input, and convert_to_webp, which is only read inside the uploader's callback.

Second, the base container carries t-if="not props.readonly". The replacement carries no such guard, so the pencil and trash buttons render on a read-only record. They will still open the dialog and still delete the child row.

Third, both buttons are gated on the field having a value. An empty media row therefore shows no controls at all, which is why the core kanban always creates rows through the subview's create action rather than expecting users to click an empty tile.

On the write side, both save paths touch fields the widget names literally. An image save runs a search_read on ir.attachment for datas and name, refuses attachments of URL type with a warning because they carry no datas, then writes the binary into your field and the attachment name into name. A video save builds the platform URL and writes video_url plus a name such as YouTube - [Video].

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. The development branch adds two options and reworks both save paths; see below.
Odoo 19.0VerifiedVerified against the shipped source. File moved to html_editor/static/src/fields/x2many_field/ and the media dialog is limited to Images and Videos.
Odoo 18.0VerifiedFirst version. Lived at html_editor/static/src/others/ and opened the media dialog with noIcons instead of a tab whitelist.
Odoo 17.0Not availableWidget does not exist. The html_editor module was introduced in Odoo 18.
Odoo 16.0Not availableWidget does not exist.

Upgrade note. The widget arrived in Odoo 18 with the html_editor module and lived at html_editor/static/src/others/; in Odoo 19 the file moved to html_editor/static/src/fields/x2many_field/. The registered name never changed, so view XML needs no edit. The one behavior change between 18 and 19 is the media dialog scope: 18 passed noIcons: true and left the document tab reachable, 19 restricts the dialog to Images and Videos.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. The development branch is unstable and anything below can still change before release; we re-verify this page once Odoo 20 ships.

Two new options appear, and like the rest of this widget's knobs they are read in extractProps without being declared, so tooling will not surface them: set_attachment_id, which stores the chosen attachment's id instead of only its binary, and only_image, which drops the Videos tab and leaves the dialog on Images.

Two behavior changes. The image save path moves to a shared saveSingleAttachment helper, which retires the current refusal of URL-type attachments. And video saves now fetch the platform thumbnail and store it into image_1920, so a video tile stops being a blank card.

Common problems and fixes

SymptomCause and fix
Picking a video does nothingThe subview does not load a field called video_url. Add <field name="video_url" invisible="1"/> to the subview. The widget writes that field by name.
The record name keeps changing by itselfExpected behavior. An image save also writes the attachment's file name into the record's name field. Rename after picking the image, or accept the attachment name as the media label.
Warning: cannot add URL type attachmentThe chosen attachment is a URL record with no stored binary, typically a demo image. Re-upload the file so a real attachment exists, then pick it again.
accepted_file_extensions is ignoredThe widget's template removes the file uploader that reads that option. Filter at the media dialog or server side instead; the option cannot work here.
The delete button removed the whole gallery entryExpected behavior. Unlike the image widget, this one deletes the child record from the parent relation. Discard the form to recover, and brief users that the trash icon removes the media item.
Edit and delete buttons appear on a read-only formThe replaced template drops the base widget's readonly guard. Hide the field itself with an invisible expression when the record must not be edited.
An empty tile has no buttonsBoth buttons render only when the field already holds a value. Create media rows through the subview's add action rather than expecting users to click an empty tile.

X2_many_image field vs the alternatives

WidgetBest forKey difference
x2_many_imageMedia galleries where each child record is one image or one video linkEdits through Odoo's media dialog, and deleting removes the child record rather than clearing the image
imageA single picture stored on the record itselfUploads from the file picker and its clear button blanks the field instead of deleting a row
x2_many_media_viewerAdding several media items at once into a relationMulti-select media dialog that creates many child records in one pass
video_previewShowing the embedded player for a stored videoRead-only preview of server-computed embed code, no picking involved
many2many_binaryA plain list of attached filesFile list with an upload button, no image rendering and no media dialog

The deciding question is what the button should do. If users pick from files on their machine, the plain image widget is correct. If they should pick from what Odoo already holds, or paste a video link, this widget is correct. And if the relation carries several media per row rather than one image per row, x2_many_media_viewer is the multi-select sibling.

Frequently asked questions

Can I use x2_many_image on a normal form field?+
It will render, but the delete button calls into the parent relation and will fail or do nothing outside an x2many subview. On a standalone field use the plain image widget.
Which video platforms are accepted?+
Whatever Odoo's media dialog recognizes on your version, typically YouTube, Vimeo, Dailymotion, Instagram and Facebook. The widget itself only stores the URL the dialog returns into video_url.
Why does my custom media model not save?+
The widget writes two field names literally: name for the label and video_url for the video link. A model that names them differently renders correctly and then silently loses the write.
Does convert_to_webp work here?+
No. It is inherited from the image widget but only consumed by the file uploader, and this widget's template removes the uploader. We verified that in the Odoo 19 source.
Is the widget available in Odoo 17?+
No. It arrived in Odoo 18 together with the html_editor module. On 17 and 16 the product media kanban used a different arrangement.

Want a product catalog your team actually maintains?

Media galleries, video-enabled product pages and clean catalog data are where most Odoo eCommerce projects lose momentum. We build and tune the product side of Odoo 16 through 19, including custom media models that behave correctly with widgets like this one.

Book a free consultation

How this page was produced

The option behavior, the hardcoded name and video_url writes, and the three effects of the replaced template were read from x2many_image_field.js and x2many_image_field.xml in the Odoo 19.0 branch, cross-read against image_field.js and image_field.xml in the same branch to establish what is inherited. The Odoo 18 comparison comes from the same file at its older path. The Odoo 20 notes come from the public development branch. Spotted an error? Tell us and we will correct the page.