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.
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
| Attribute | fields.Image | fields.Binary |
|---|---|---|
| Available since | Odoo 13 (stable, recommended from Odoo 14+) | All Odoo versions |
| Storage | Base64-encoded bytes in the database (same as Binary) | Base64-encoded bytes in the database |
| Auto-resize | Yes — max_width and max_height auto-resize on save | No — stores whatever bytes you pass |
| Automatic thumbnail | Yes — Odoo generates image_128 and image_256 shadow fields automatically | No — manual thumbnail fields required |
| Best for | Any 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.
my_module/
├── __manifest__.py
├── __init__.py
├── models/
│ └── my_model.py
├── static/
│ └── img/
│ └── default_avatar.png ← default image file goes hereSetting 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`.
# 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_1920Setting 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.
# 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.
# 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.
<!-- 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.
# __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
| Symptom | Cause | Fix |
|---|---|---|
| New records are created without the default image | The default function was called at class definition time instead of passed as a callable — e.g. default=_default_image() instead of default=_default_image | Remove the parentheses: default=_default_image (pass the function object, not its return value). |
| FileNotFoundError or get_module_resource returns None | The image file path is wrong — the file doesn't exist at static/img/default_avatar.png relative to the module root | Verify 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 production | The 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 image | The field is fields.Binary without widget='image' in the view XML, or the image is accidentally stored as a Python str instead of bytes | 1) 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 value | The 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 logic | Re-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 | Key changes affecting default image on binary/image fields |
|---|---|
| Odoo 14 & 15 | fields.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 16 | file_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 17 | No 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 & 19 | No 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.
Frequently Asked Questions
How do I set a default image for a fields.Image field in Odoo?
What is the difference between fields.Image and fields.Binary for storing images in Odoo?
Where should I store the default image file inside my Odoo module?
How do I add a placeholder image in an Odoo form view without storing a default image in the database?
How do I back-fill a default image on existing records after adding a default to an image field?
Why does my default image show in development but not after deploying to Odoo.sh?

iWesabe Editorial Team
Practitioner insights on Odoo ERP, ZATCA compliance, and Saudi enterprise digital operations — written by iWesabe's consulting, finance, and engineering teams.
Related Articles
How to Show an Image in QWeb Odoo — Web, Reports & Portal (Odoo 16–19)
A complete developer reference for rendering images in Odoo QWeb templates: the /web/image controller, t-att-src patterns, t-field widget='image', PDF report images, and common troubleshooting. Verified for Odoo 18 & 19.
Access Security in Odoo — ir.model.access.csv and Record Rules (Odoo 16–19)
A complete developer reference for Odoo's two-layer security system: model-level CRUD permissions via ir.model.access.csv and record-level filtering via ir.rule. Verified for Odoo 18 & 19.
How to Deploy Community Modules on Odoo.sh (Odoo 16–19)
A complete developer reference for deploying custom and OCA community modules on Odoo.sh: GitHub repository setup, branch workflow, submodules, requirements.txt, build logs, and staging-to-production promotion. Verified for Odoo 18 & 19.
Explore Related Solutions
CRM & Customer Relationship Management
Turn every lead into a closed deal. Odoo CRM gives your sales team a structured Kanban pipeline, automated follow-up sequences, and a 360° customer view — all connected natively to Accounting, Inventory, and Email Marketing.
ExploreFinancial Management
Automate your Saudi Arabia accounting — ZATCA Phase 2 e-invoicing, VAT returns, Zakat provisioning, multi-currency bank reconciliation, and real-time financial reporting — all inside one connected Odoo platform. Fully localised for KSA and the GCC by iWesabe.
ExploreManufacturing Management ERP
Run lean, traceable production with Odoo Manufacturing — work orders, Bills of Materials, quality control, and real-time OEE dashboards built for KSA factory floors.
ExploreSupply Chain Management
From purchase order to final delivery — Odoo Supply Chain connects procurement, multi-warehouse inventory, demand forecasting, and vendor management into one real-time platform. Purpose-configured for Saudi Arabia and the GCC by iWesabe.
Explore