Skip to main content
iVentureTeam

background_image

The large employee photo panel in Odoo HR is not the standard image widget: background_image inherits every image option but swaps the template for a bare, display-only <img> with no upload or delete controls.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 19, 2026Updated August 19, 20265 min read
Odoo 19 employee form showing the photo panel rendered by the background_image widget with the zoom tooltip enabled.
Technical namebackground_image
Field typesbinary, many2one (inherited from the image field)
Viewsform, kanban
Modulehr (Employees)
Used in core6 occurrences across 2 modules, including hr and hr_org_chart, on employee photos
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0, Odoo 17.0, Odoo 16.0
No-code setupNo: applying it to a field is an XML-only change
Alternativesimage, image_url, many2one_avatar_employee

What the Background Image widget does

The employee form in Odoo does something subtle with photos: the picture you see in the header panel is not editable in place. Upload and removal happen through the standard avatar field elsewhere; the big display is background_image, a deliberately stripped-down cousin of the image widget.

The JavaScript is five lines: extend ImageField, point it at a different template, re-register. All behavior, options and even the zoom tooltip machinery are inherited. What changes is the template: instead of the image frame with its hover controls for uploading, editing and deleting, this one renders a single <img loading="lazy"> whose source is resolved by the inherited getUrl, honoring preview_image to serve a smaller variant.

The name comes from how HR uses it: the employee form styles the image as a full-bleed background panel behind the name block, and the org chart cards do the same. The widget itself does not apply any background positioning; that comes from the surrounding HR styles, which is worth knowing before borrowing it elsewhere.

What this means for your team

The pattern this widget embodies matters more than the widget: separate where an image is managed from where it is displayed.

On the employee form, the photo appears twice: once as the editable avatar, once as the large panel. Making the large panel editable too would invite accidental drag-and-drop replacements and confuse whose photo pipeline (resizing, WebP variants) actually runs. The display-only copy costs nothing and cannot be broken by a user.

We reuse the same trick in client projects: product images on operational dashboards, site photos on field-service tasks, signatures on approval screens, anywhere staff must see an image but should never change it from that screen. Because it accepts a preview_image field, the display copy can also load a smaller resized variant instead of the full upload, which keeps image-heavy kanbans fast.

Supported options in Odoo 19

Verified against background_image.js in the Odoo 19.0 hr module, which spreads the full imageField descriptor from web. Every option below is therefore inherited from the image widget; the table notes which ones are effectively inert under this widget's display-only template.

OptionTypeWhat it does
preview_imagefield name (binary)Inherited. Serves this field's smaller variant (e.g. image_128) as the displayed source instead of the full stored image. Mandatory when the widget is placed on a many2one field.
zoombooleanInherited. Shows the full image in a hover tooltip. Core's employee form enables it so the small panel can reveal the full photo.
zoom_delaynumber (ms)Inherited. Milliseconds before the zoom tooltip appears; the employee form uses 1000.
sizeselection [width, height]Inherited. Max-width/max-height constraints ([0,90], [0,180], [0,270] in Studio terms) applied as inline style limits.
reloadbooleanInherited. When false, the widget keeps serving the first URL it computed instead of following value changes.(default: true)
convert_to_webpbooleanInherited but inert here: it acts during upload, and this widget's template provides no upload path.
accepted_file_extensionsstringInherited but inert here for the same reason: no file picker is ever opened from this widget.(default: image/*)
img_classstring (undocumented)Inherited from the image widget's extractProps, where it is read but declared nowhere. Inert under this template, which does not apply imgClass; use the class attribute on the field element instead.

Inherited does not mean usable. The descriptor accepts all image options, but the template has no upload path, so convert_to_webp and accepted_file_extensions can never trigger, and the alt attribute is accepted but the template hardcodes alt="Binary file". The options that actually change what you see are zoom, zoom_delay, preview_image, size and reload.

Working examples

As the employee form uses it

<field name="image_1024" widget="background_image"
       options="{'zoom': true, 'zoom_delay': 1000, 'preview_image': 'image_128'}"
       class="d-block position-relative"/>

Displays the 128px variant for speed, zooms to the stored image on hover after one second.

A read-only product photo on an operations view

<field name="product_image" widget="background_image"
       options="{'preview_image': 'product_image_256'}"/>

Staff see the photo; nobody can replace it from this screen. Requires the hr module (or copying the five-line widget into your own).

What it inherits, including the sharp edges

Because everything is inherited, the sharp edges of this widget are actually the image widget's, and two of them bite harder here.

The many2one mode inherits its crash. The image field supports binary and many2one types, and for many2one it throws unless preview_image is provided, an inherited check that fires identically here. If you point background_image at a many2one, set preview_image to the image field on the target model or the view will not render.

Cache-busting still follows write_date. The inherited URL builder appends the record's write_date as a cache key (the descriptor even declares a write_date field dependency), so a photo updated through the editable avatar refreshes here on the next load without stale-cache tricks. On related fields pointing at another record, that key is skipped, which can leave a stale image until a hard refresh, again inherited behavior worth knowing when displaying images across models.

Empty values render the placeholder image, the standard gray silhouette from /web/static/img/placeholder.png, because the inherited getUrl falls back to it. There is no way to render nothing instead without an invisible condition on the field.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. The source file on the development branch is byte-identical to 19.0.
Odoo 19.0VerifiedVerified against the shipped source.
Odoo 18.0VerifiedIdentical wrapper apart from the module pragma line.
Odoo 17.0VerifiedPresent and functionally identical.
Odoo 16.0VerifiedPresent with the older class-assignment syntax; same behavior.

Upgrade note. The widget has been a five-line wrapper since Odoo 16; only the wrapper syntax modernized (class fields in 19 versus assignment style in 16). XML using it carries across versions untouched.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. The notes below are read from the public development branch and are not final until release.

No changes: the source file on master is byte-identical to Odoo 19. Any evolution will come through the parent image widget it spreads, so watch that page's Odoo 20 section rather than this one. We re-verify against the shipped release.

Common problems and fixes

SymptomCause and fix
Users cannot change the photo from this widgetBy design: the template is a bare img with no upload or delete controls. Edit the image through the standard image widget elsewhere in the view (the avatar field on employees).
The view crashes when the widget sits on a many2one fieldThe inherited image logic throws unless preview_image names the image field on the target model. Add options="{'preview_image': 'image_128'}" (or the relevant field) to the widget.
An empty field shows a gray placeholder silhouetteThe inherited URL fallback serves /web/static/img/placeholder.png when the value is empty. Hide the field with an invisible condition when empty if the placeholder is unwanted.
The image looks stretched or overflows its panelThe widget applies no background sizing itself; the framing comes from surrounding classes. Add sizing classes on the field element (as core does with d-block position-relative) or the size option.
An updated image does not refresh on a related fieldThe write_date cache key is skipped when the image lives on another record, an inherited behavior. Display the image from its own record where possible, or accept a refresh delay.

Background Image widget vs the alternatives

WidgetBest forKey difference
background_imageShowing an image users must not edit in placeFull image option set with the upload and delete controls removed
imageImages users upload and manageSame engine plus the upload, edit and delete controls and WebP pipeline
image_urlImages hosted outside OdooRenders from a URL in a char field; nothing is stored in the database
many2one_avatar_employeeA small employee photo next to their nameAvatar chip on a relational field, not a display panel

Decide by who may touch the image: users manage it through image, merely see it through background_image. For images hosted outside Odoo, image_url displays from a URL without storing binary data at all.

Frequently asked questions

Why can't I upload a photo through the big image on the employee form?+
That panel is the background_image widget, which is display-only by design. Change the photo through the employee's avatar field; the panel refreshes automatically thanks to the write_date cache key.
What is the difference between background_image and image?+
One template. background_image spreads the image widget's full descriptor but renders a bare lazy-loading img with no upload, edit or delete controls. Options that act at upload time are therefore inert.
Can I use background_image outside HR?+
Yes, on any binary image field, as long as the hr module is installed since it owns the registration. On non-HR databases, copying the five-line widget into your module is the cleaner route.
Does it support zooming into the image?+
Yes, the inherited zoom and zoom_delay options work: hovering shows the full image in a tooltip. The employee form ships with zoom enabled and a one-second delay.
Why does it load a small image first?+
Core passes preview_image: image_128, so the panel loads the resized variant instead of the full upload. That is an inherited option worth copying on any image-heavy view.

Views that show exactly what each role should touch?

Display-only images, locked fields and role-shaped forms are how an Odoo becomes safe to hand to a whole company. We take that discipline through your views screen by screen, deciding per role what is visible, what is editable, and what loads fast.

Shape our views by role

How this page was produced

This page was verified by reading background_image.js and its template in the Odoo 19.0 hr module, together with the parent image_field.js in web whose descriptor it spreads, including the many2one preview_image guard and the write_date cache key described above. The employee form usage was read from the shipped hr views. Version stability was confirmed against the 16.0 branch and the byte-identical file on the development branch. Spotted an error or a version difference? Tell us and we will correct the page.