Odoo Development

How to Set a Default Image for a Binary Field in Odoo (16–19)

A complete developer reference for setting default images on Odoo binary and image fields: fields.Image vs fields.Binary, the file_open + base64 pattern, _default_image methods, module static assets, and the image widget placeholder attribute. Verified for Odoo 18 & 19.

iWesabe Editorial TeamMay 9, 20209 min read

Setting a default image on an Odoo binary or image field is a common requirement: a default avatar for new contacts, a placeholder product photo, or a company logo pre-filled on a new record. The implementation approach changed significantly between Odoo 14 and Odoo 16 with the introduction of `fields.Image` as a dedicated image field type. This guide covers both the modern `fields.Image` approach (Odoo 16+) and the legacy `fields.Binary` approach, so you can handle both new modules and legacy code maintenance.

fields.Image vs fields.Binary — Which to Use

Comparison of Odoo image storage field types
Attributefields.Imagefields.Binary
Available sinceOdoo 13 (stable, recommended from Odoo 14+)All Odoo versions
StorageBase64-encoded bytes in the database (same as Binary)Base64-encoded bytes in the database
Auto-resizeYes — max_width and max_height auto-resize on saveNo — stores whatever bytes you pass
Automatic thumbnailYes — Odoo generates image_128 and image_256 shadow fields automaticallyNo — manual thumbnail fields required
Best forAny image (avatar, product photo, company logo). Use this for all new development.Non-image binary files (PDF attachments, Excel exports). Also used in legacy Odoo 12 and earlier modules.

Storing Default Images in Your Module

The default image file must ship inside your module directory. The standard location is `static/img/` — this mirrors the pattern used by Odoo's own modules (e.g. `mail/static/img/default_image.png`). The file is read at module installation time (or when the default function is called for a new record) and converted to a base64 string stored in the database.

text
my_module/
├── __manifest__.py
├── __init__.py
├── models/
│   └── my_model.py
├── static/
│   └── img/
│       └── default_avatar.png   ← default image file goes here

Setting a Default Image on fields.Image (Modern — Odoo 14+)

The recommended pattern uses a module-level function to read the image file and return it as a base64-encoded bytes value. This function is then passed to the `default=` parameter of `fields.Image`.

python
# models/my_model.py
import base64
from odoo import fields, models
from odoo.modules.module import get_module_resource


def _default_image():
    """Read the bundled default avatar and return it as base64 bytes."""
    img_path = get_module_resource("my_module", "static", "img", "default_avatar.png")
    with open(img_path, "rb") as f:
        return base64.b64encode(f.read())


class MyModel(models.Model):
    _name = "my.model"
    _description = "My Model"

    name = fields.Char(required=True)

    image_1920 = fields.Image(
        string="Image",
        max_width=1920,
        max_height=1920,
        default=_default_image,   # ← pass the function object, not the call
    )
    # Odoo auto-generates image_128 and image_256 shadow fields from image_1920

Setting a Default Image on fields.Binary (Legacy Pattern)

In Odoo 12 and 13 modules (or legacy code you are maintaining), the same pattern applies using `fields.Binary`. The only difference is the field declaration — the `_default_image` function and `get_module_resource` call are identical.

python
# models/my_model.py  (Odoo 12/13 legacy pattern)
import base64
from odoo import fields, models
from odoo.modules.module import get_module_resource


def _default_image():
    img_path = get_module_resource("my_module", "static", "img", "default_avatar.png")
    with open(img_path, "rb") as f:
        return base64.b64encode(f.read())


class MyModel(models.Model):
    _name = "my.model"
    _description = "My Model"

    name = fields.Char(required=True)

    image = fields.Binary(
        string="Image",
        attachment=True,   # store in ir.attachment instead of database column
        default=_default_image,
    )
    image_medium = fields.Binary(
        string="Medium Image",
        attachment=True,
    )
    image_small = fields.Binary(
        string="Small Image",
        attachment=True,
    )
    # In legacy Odoo 12/13, you had to generate resized variants yourself.
    # In Odoo 14+ with fields.Image, this is automatic.

Alternative: Using file_open Instead of get_module_resource

A slightly shorter alternative is `odoo.tools.misc.file_open`, which accepts a module-relative path and handles both development and production (zip-bundled) module layouts. Both approaches work — choose the one that matches your codebase's existing style.

python
# Alternative using file_open
import base64
from odoo import fields, models
from odoo.tools.misc import file_open


def _default_image():
    # file_open path: <module_name>/<relative_path_within_module>
    with file_open("my_module/static/img/default_avatar.png", "rb") as f:
        return base64.b64encode(f.read())


class MyModel(models.Model):
    _name = "my.model"
    _description = "My Model"

    image_1920 = fields.Image(
        string="Image",
        max_width=1920,
        max_height=1920,
        default=_default_image,
    )

The placeholder Attribute — Show a Default Image in the View Without Storing It

Sometimes you want a visual placeholder in the form view when no image is set, without pre-filling the database field for every new record. The `placeholder` attribute on the `` element does exactly this — it shows a fallback image in the UI only, and does not write anything to the database.

xml
<!-- views/my_model_views.xml -->
<record id="view_my_model_form" model="ir.ui.view">
    <field name="name">my.model.form</field>
    <field name="model">my.model</field>
    <field name="arch" type="xml">
        <form string="My Model">
            <sheet>
                <field name="image_1920"
                       widget="image"
                       class="oe_avatar"
                       placeholder="/my_module/static/img/default_avatar.png"/>
                <group>
                    <field name="name"/>
                </group>
            </sheet>
        </form>
    </field>
</record>

Back-filling the Default Image on Existing Records

When you add a `default=_default_image` to an existing field on an already-deployed module, the default only applies to new records created after the module update. Existing records that had no image before the update will still have no image. To back-fill existing records, add a `post_init_hook` or a migration script.

python
# __manifest__.py
{
    "name": "My Module",
    "version": "17.0.1.1.0",
    "post_init_hook": "post_init_hook",   # ← add this
    ...
}

# __init__.py
from .hooks import post_init_hook   # ← add this

# hooks.py  (new file)
import base64
from odoo.modules.module import get_module_resource


def post_init_hook(env):
    """Back-fill default image on records that have none."""
    img_path = get_module_resource("my_module", "static", "img", "default_avatar.png")
    with open(img_path, "rb") as f:
        default_image = base64.b64encode(f.read())

    records_without_image = env["my.model"].search([("image_1920", "=", False)])
    records_without_image.write({"image_1920": default_image})

Troubleshooting

Common issues when setting default images on Odoo binary/image fields
SymptomCauseFix
New records are created without the default imageThe default function was called at class definition time instead of passed as a callable — e.g. default=_default_image() instead of default=_default_imageRemove the parentheses: default=_default_image (pass the function object, not its return value).
FileNotFoundError or get_module_resource returns NoneThe image file path is wrong — the file doesn't exist at static/img/default_avatar.png relative to the module rootVerify the file exists: ls <addons_path>/my_module/static/img/. Check for typos in the filename. get_module_resource returns None (not an error) if the file isn't found — add a check: assert img_path, 'Default image not found'.
Default image shows for new records in dev but not in productionThe module's static files were not included in the deployment (missing from the git commit or Odoo.sh build)Confirm static/img/default_avatar.png is committed to the repository (git status). On Odoo.sh, check the build log — the file must be in the repository tree, not gitignored.
Image field stores base64 string as text instead of showing the imageThe field is fields.Binary without widget='image' in the view XML, or the image is accidentally stored as a Python str instead of bytes1) Add widget='image' to the <field> element in the view. 2) In the default function, confirm base64.b64encode() returns bytes. Do not decode to str — fields.Image and fields.Binary expect bytes.
image_128 / image_256 shadow fields are empty even though image_1920 has a valueThe image was written directly to the database (e.g. via a SQL script or low-level ORM write that bypassed field compute) without triggering the fields.Image resize logicRe-write the field through the ORM: records.write({'image_1920': the_image_bytes}). The ORM write triggers the resize and shadow field population. Direct SQL inserts bypass this.

Version Notes

Odoo version notes for default image on binary/image fields
Odoo versionKey changes affecting default image on binary/image fields
Odoo 14 & 15fields.Image stabilised and recommended for all new image fields. fields.Binary _default_image pattern still works but is considered legacy. image_128 and image_256 shadow fields are auto-generated by fields.Image. get_module_resource available in odoo.modules.module.
Odoo 16file_open moved from odoo.tools to odoo.tools.misc — update imports if migrating from Odoo 15. fields.Image and the default= pattern unchanged. The image_1920 / image_128 / image_256 naming convention for avatar/contact images is now standard across the Odoo codebase.
Odoo 17No breaking changes to fields.Image or the default= pattern. post_init_hook and migration scripts for back-filling unchanged. The image widget in form views accepts placeholder as before.
Odoo 18 & 19No breaking changes. fields.Image with default=_default_image works identically. get_module_resource and file_open paths unchanged. max_width/max_height auto-resize and shadow field generation unchanged.

Need Custom Odoo Development for Your Business?

Our certified Odoo team builds custom modules, migrates legacy codebases to Odoo 17/18, and delivers full implementations across Saudi Arabia — including ZATCA Phase 2 integration, Arabic UI, GOSI, and WPS-Mudad compliance.

WhatsApp

Frequently Asked Questions

How do I set a default image for a fields.Image field in Odoo?
Define a module-level function that reads your image file and returns it as base64-encoded bytes, then pass that function to the `default=` parameter of `fields.Image`. Use `get_module_resource('my_module', 'static', 'img', 'default.png')` to get the file path, open it in binary mode, and return `base64.b64encode(f.read())`. Store the image file at `my_module/static/img/default.png`. Always pass the function object (`default=_default_image`), not the result of calling it (`default=_default_image()`).
What is the difference between fields.Image and fields.Binary for storing images in Odoo?
`fields.Image` (introduced in Odoo 13, recommended from Odoo 14+) auto-resizes images using `max_width`/`max_height` and automatically creates `image_128` and `image_256` thumbnail shadow fields — matching the standard Odoo pattern used by res.partner and product.template. `fields.Binary` stores any binary content without resizing or thumbnail generation. For all new image fields in Odoo 14+, use `fields.Image`. Use `fields.Binary` only for non-image binary files (PDFs, spreadsheets, etc.).
Where should I store the default image file inside my Odoo module?
Store the default image at `my_module/static/img/default_image.png` (or `.jpg`). The `static/` directory in an Odoo module is the standard location for static assets — it's served directly by the web server and is accessible at runtime. The `img/` subdirectory is the convention for image assets. Both `get_module_resource` and `file_open` will find files here correctly in both development and production (Odoo.sh) environments.
How do I add a placeholder image in an Odoo form view without storing a default image in the database?
Use the `placeholder` attribute on the `` element in your form view XML: ``. The placeholder path is a URL path relative to the Odoo base URL — it is served from the module's static directory. The placeholder only shows in the UI when the field is empty; it does not write anything to the database.
How do I back-fill a default image on existing records after adding a default to an image field?
The `default=` parameter only applies to new records — existing records with no image are unaffected by adding or changing the default. To back-fill existing records: (1) for a fresh install, add a `post_init_hook` in `__manifest__.py` that searches for records with no image and writes the default to them via the ORM; (2) for an upgrade, use a migration script at `migrations//post-migrate.py` with a `migrate(cr, version)` function that performs the same back-fill. Always use ORM writes (`records.write({...})`) rather than SQL — ORM writes trigger the resize and shadow field population.
Why does my default image show in development but not after deploying to Odoo.sh?
The most common reason is that the image file was not committed to the git repository. On Odoo.sh, only files in the repository are available at runtime — any file you added locally but forgot to commit will be missing. Run `git status` to check. Also verify the file is not in `.gitignore`. After confirming the file is committed, push to Odoo.sh — the next build will include it. As a secondary check, ensure the path passed to `get_module_resource` or `file_open` matches the actual file path in the repository exactly (case-sensitive on Linux).
iWesabe Editorial Team

iWesabe Editorial Team

Practitioner insights on Odoo ERP, ZATCA compliance, and Saudi enterprise digital operations — written by iWesabe's consulting, finance, and engineering teams.

About iWesabe

Related Articles