Skip to main content
iVentureTeam

contact_image

New in Odoo 19: the photo frame on contact and user forms is its own widget. contact_image extends the image field with one trick, when the photo is empty it shows the record's generated avatar and a centered Add Photo button.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 25, 2026Updated August 25, 20266 min read
Odoo 19 contact form showing the contact_image widget with the generated initials avatar and the centered Add Photo button.
Technical namecontact_image
Field typesbinary, many2one
Viewsform
Moduleweb, present in every Odoo database
Used in core3 occurrences in hr's user form, plus 5 more on the base res.partner and res.users forms
VersionsOdoo 20.0, Odoo 19.0
No-code setupNo. Applied in view XML together with an invisible preview field
Alternativesimage, attachment_image, background_image

What the Contact Image field does

Odoo 19 gave the contact photo its own widget. contact_image extends the standard image field component and changes exactly two things, both about the empty state.

First, the fallback. The base image widget shows a placeholder frame when the binary field is empty. contact_image instead reads the field named by the preview_image option, avatar_128 on every core usage, and displays that. Because avatar_128 on partners and users is computed server side into the colored initials avatar when no photo exists, the form always shows something person-shaped rather than a gray frame.

Second, the controls. With no valid photo, the usual small pencil button is replaced by a centered Add Photo button laid over the avatar, and the overlay is dimmed to quarter opacity until you hover. With a valid photo, the widget behaves exactly like image: edit and clear buttons, upload dialog, the same option set.

What this means for your team

Faces matter in a CRM. Teams recognize customers faster, pick the right John from a list, and trust records that look maintained. The obstacle was always the empty state: a database of gray placeholder frames looks abandoned, and nobody feels invited to click a tiny pencil icon.

This widget is Odoo's answer. Every contact shows the generated avatar from day one, so the database never looks empty, and the oversized Add Photo button turns uploading from a hidden feature into an obvious next step. If your company builds custom directory-style models, employees, members, drivers, tenants, reusing this widget plus a computed avatar field gives you the same polished behavior for free.

Supported options in Odoo 19

The widget declares no options of its own: its descriptor spreads the image field descriptor, so the entire option set below is inherited. All verified in image_field.js and contact_image_field.js, Odoo 19.0. The one that changes meaning here is preview_image: on plain image it is a bandwidth optimization for display, on contact_image it is also the empty-state fallback image, so in practice it is required.

OptionTypeWhat it does
preview_imagefield nameBinary field rendered instead of the main field, and, specific to this widget, shown as the empty-state fallback. Core always passes avatar_128 and loads it invisibly in the view. Effectively required here.
sizeselection [width,height]Display box as [width, height]; a zero side is treated as unset. Core contact forms use [130,130] and [100,100]. The size option beats width and height attributes.
zoombooleanShows an enlarged preview on hover. The base users form enables it; combine with zoom_delay to tune the hover delay.
zoom_delaynumber (ms)Milliseconds before the hover zoom appears. Only meaningful together with zoom.
accepted_file_extensionsstringComma-separated extension filter passed to the file picker for uploads, for example '.png,.jpg'.
convert_to_webpbooleanConverts the uploaded image to WebP and generates the resized attachment set, same pipeline as the image widget.
reloadbooleanReloads the image after saving so the freshly stored version replaces the local preview.(default: true)
img_classstring (undocumented)Extra CSS classes for the img element, read only in extractProps and absent from the declared options. Core passes rounded-4 on user forms and rounded border on partner forms.

The preview field must be loaded in the view. Every core usage places <field name="avatar_128" invisible="1"/> right next to the widget, with the source comment Needed in contact_image widget. Omit it and the fallback silently renders nothing, because the record data simply does not contain the avatar.

Working examples

The core pattern, verbatim from the partner form

<field name="image_1920" widget="contact_image"
       options="{'preview_image': 'avatar_128', 'size': [130,130], 'img_class': 'rounded border'}"/>
<field name="avatar_128" invisible="1"/> <!-- Needed in contact_image widget -->

The invisible companion field is not optional decoration; it is what loads the avatar into the record data the widget reads.

Rounded user-form variant with zoom

<field name="image_1920" widget="contact_image"
       options="{'preview_image': 'avatar_128', 'zoom': true, 'size': [130,130], 'img_class': 'rounded-4'}"/>

Exactly what the base users form ships. img_class takes any CSS classes for the img element; it appears in no options panel and only in extractProps.

How the avatar fallback actually works

The fallback logic in getUrl is worth reading precisely, because it explains two behaviors people mistake for bugs. The override only kicks in when the main field is empty or the uploaded file failed validation. In that state it takes the preview field's value: if the record is saved, it builds a normal /web/image URL for it; if the value is an unsaved base64 payload, it embeds it directly as a data URL, hardcoded as PNG in 19.

Consequence one: a user who uploads a broken file does not get a broken frame, they silently fall back to their initials avatar plus the Add Photo button, and only the validation state knows anything went wrong. Consequence two: the widget never shows the base widget's static placeholder on contacts, because partners and users always have a computed avatar_128. On a custom model without such a computed field, the empty state shows nothing but the Add Photo button, which is your cue to add an avatar-style computed binary rather than to debug the widget.

The class getter adds opacity-100 opacity-25-hover while there is no valid image, producing the dimmed-until-hover effect, and the template moves the upload button from the frame's bottom edge to dead center. All of it is empty-state choreography; a filled photo renders through the parent class untouched.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. Internals reworked on the development branch; options unchanged. See below.
Odoo 19.0VerifiedIntroduced here and adopted across partner, user and hr preference forms.
Odoo 18.0Not availableWidget does not exist; the same forms use image with preview_image.
Odoo 17.0Not availableWidget does not exist.
Odoo 16.0Not availableWidget does not exist.

Upgrade note. Views written for Odoo 18 or earlier use widget="image" with preview_image on these same fields and keep working in 19 unchanged. Adopting contact_image during an upgrade is a one-word view change per field, plus the invisible avatar_128 companion if it is not already in the view.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. We read this widget's file on the public development branch at the time of writing, with the standard caveat that the branch is unstable and can change before release.

The registration and option set are unchanged, but the internals are reworked: the fallback reads the new structured binary value and detects the real image type from the file's magic bytes instead of assuming PNG, the empty-state layout moves from the button to a container class getter, and a ui service hook appears. A new test file also lands, a sign the widget is becoming a supported fixture. XML written for 19 keeps working; JavaScript patches will need review. We re-verify after release.

Common problems and fixes

SymptomCause and fix
Empty contacts show a blank frame, not the initials avatarThe preview_image field is not loaded in the view, so the fallback has no data to render. Add the invisible companion: <field name="avatar_128" invisible="1"/> next to the widget.
Widget crashes or is missing after backporting a viewcontact_image is 19.0-new; Odoo 16 to 18 have no such registration. Use widget="image" with the same options on older versions.
Custom model shows only an Add Photo button in the empty stateThe model has no computed avatar field to fall back on, unlike partners and users. Add a computed binary in the avatar style, or accept the plain empty state.
Uploaded picture seems ignoredThe file failed the image validation, and the widget quietly fell back to the avatar. Check the file really is a valid image below the field's size limit and try again.
Corners are square although the form looks rounded elsewhereThe rounding on core forms comes from the undocumented img_class option, not from the widget. Pass options="{'img_class': 'rounded-4'}" (or rounded border) like the core views.

Contact Image field vs the alternatives

WidgetBest forKey difference
contact_imageProfile photos on person-shaped forms: contacts, users, employeesFalls back to the preview_image field's generated avatar and shows a centered Add Photo button when empty
imageAny binary image with upload, zoom and sizing needsSame option set, but the empty state is a static placeholder frame with a corner pencil button
attachment_imageDisplaying one of the record's attachments, the kanban cover patternReads a many2one to ir.attachment and offers no editing at all
background_imageDecorative image display without editing chromeBare lazy img rendering; no fallback, no upload overlay

Rule of thumb: person-shaped records with a computed avatar get contact_image; everything else keeps the plain image widget, and read-only avatar chips in lists belong to the avatar family of widgets instead.

Frequently asked questions

Where does the colored initials avatar come from?+
From the server, not the widget. avatar_128 on partners and users is computed: it returns the photo when one exists and an SVG of the initials on a colored background otherwise. The widget just displays that field whenever the real photo field is empty.
Why do core views add an invisible avatar_128 field next to the widget?+
Widget options do not automatically load fields. The invisible <field name="avatar_128"/> puts the avatar into the record data so the fallback in getUrl has something to read. The source comments it Needed in contact_image widget.
Can I use contact_image on my own model?+
Yes: any binary field works, and the widget shines if you also add a computed avatar-style field to point preview_image at. Without one, the empty state is just the centered Add Photo button.
Is contact_image available in Odoo 18?+
No. It first ships in Odoo 19. On 16 through 18 the same forms use widget="image" with the preview_image option, which gives the bandwidth optimization but not the avatar fallback or the Add Photo overlay.
What sizes should the photo field use?+
Core binds the widget to image_1920 and displays it at 130x130 through the size option. Storing at 1920 keeps a high-quality original while Odoo's resized variants (image_128, avatar_128...) serve the small renditions.

Building a directory your team actually recognizes?

Employee registers, member databases and customer files all read better with faces on them. We wire up avatar fallbacks, photo pipelines and polished contact forms, contact_image included, as part of Odoo implementations on version 19.

Book a free consultation

How this page was produced

This page was verified by reading contact_image_field.js, its template, and the parent image_field.js descriptor in the Odoo 19.0 source, plus every core view using the widget in base and hr. Absence in 16 through 18 was confirmed against those branches' file trees, and the Odoo 20 notes come from diffing the development branch. Spotted an error? Tell us and we will correct the page.