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.
Rendering images in Odoo QWeb depends on where the template runs and how the image is stored. A website page, a portal template, a PDF report, and an email template each require a different approach. The most important distinction: web-rendered templates can use URLs (the `/web/image` controller), while PDF report templates require base64-encoded binary data because the wkhtmltopdf renderer has no network access to the Odoo server at render time.
Image Rendering Methods — When to Use Each
| Method | Template context | Image source | Notes |
|---|---|---|---|
| /web/image URL + t-att-src | Website, portal, web views, email (external send) | Binary/Image field on any model | Most flexible. Odoo serves the image as HTTP. Supports resize parameters. |
| t-field widget='image' | PDF reports (qweb-pdf) only | Binary/Image field | Renders the field value as an inline base64 img tag. Required for PDF — wkhtmltopdf cannot fetch URLs. |
| Static asset (t-att-src with /web/static/) | Any QWeb template | File in module's static/src/ directory | For module-bundled images (logos, icons). Path: /web/static/src/<module>/img/<file>. |
| Inline base64 (t-att-src with data URI) | PDF reports, email templates | Base64 string computed in Python | Fallback when t-field is not available or for dynamic computed images. |
Part 1 — The /web/image Controller (Web & Portal)
The `/web/image` controller is Odoo's built-in HTTP endpoint for serving binary field values as images. It handles caching, access control (respecting record rules), and optional server-side resizing. This is the correct method for any web-rendered template — website pages, portal templates, Kanban cards, form view widgets, and external emails.
/web/image URL Format
# Basic form — model + record ID + field name
/web/image/<model>/<id>/<field>
# With optional width/height resize
/web/image/<model>/<id>/<field>/<width>x<height>
# Using record external ID instead of integer ID
/web/image/<model>/<external_id>/<field>
# Examples:
/web/image/res.partner/42/image_1920
/web/image/product.product/7/image_1920/128x128
/web/image/res.company/1/logot-att-src Patterns in QWeb Templates
<!-- Pattern 1: Image field on the current record (e.g. inside t-foreach or a record template) -->
<img t-att-src="'/web/image/%s/%s/image_1920' % (object._name, object.id)"
alt="Partner Image"
style="max-width: 200px;"/>
<!-- Pattern 2: Specific model + ID (hardcoded or from a Python variable) -->
<img t-att-src="'/web/image/res.partner/' + str(partner.id) + '/image_1920'"
alt="Partner"/>
<!-- Pattern 3: With server-side resize (128×128 thumbnail) -->
<img t-att-src="'/web/image/product.product/%d/image_1920/128x128' % product.id"
alt="Product thumbnail"/>
<!-- Pattern 4: Company logo in a header -->
<img t-att-src="'/web/image/res.company/%d/logo' % company.id"
alt="Company Logo"
style="height: 60px;"/>
<!-- Pattern 5: Conditional — only render img if the field has a value -->
<img t-if="partner.image_1920"
t-att-src="'/web/image/res.partner/%d/image_1920' % partner.id"
alt="Partner Image"/>Part 2 — Images in QWeb PDF Reports
PDF reports in Odoo are generated by wkhtmltopdf, which renders the QWeb template as HTML and converts it to PDF. The critical constraint: wkhtmltopdf runs as a separate process and cannot make HTTP requests back to the Odoo server. Any `` in a PDF report will result in a broken image. The solution is `t-field` with `widget='image'`, which instructs Odoo to render the field value as an inline base64 data URI before passing the HTML to wkhtmltopdf.
<!-- CORRECT: t-field with widget='image' for PDF reports -->
<t t-foreach="docs" t-as="o">
<!-- Company logo in report header -->
<img t-field="o.company_id.logo"
t-options="{'widget': 'image', 'class': 'company_logo'}"
style="max-height: 60px;"/>
<!-- Product image in report line -->
<td>
<img t-field="o.product_id.image_128"
t-options="{'widget': 'image'}"
style="width: 64px; height: 64px;"/>
</td>
<!-- Partner photo — only if present -->
<t t-if="o.partner_id.image_1920">
<img t-field="o.partner_id.image_1920"
t-options="{'widget': 'image', 'class': 'partner_photo'}"
style="width: 100px;"/>
</t>
</t>
<!-- WRONG (broken image in PDF): -->
<!-- <img t-att-src="'/web/image/res.partner/%d/image_1920' % o.partner_id.id"/> -->Part 3 — Static Module Images (Logos, Icons)
For images that are part of your module (not stored in the database) — such as a default logo, a watermark, or a decorative graphic — store them in `static/src/img/` and reference them with a static path. These paths work in both web templates and PDF reports because wkhtmltopdf can resolve local filesystem paths.
<!-- Static module image in any QWeb template (web OR PDF report) -->
<!-- Method A: absolute web path -->
<img src="/my_module/static/src/img/logo.png"
alt="My Module Logo"
style="height: 50px;"/>
<!-- Method B: t-att-src with module path concatenation -->
<img t-att-src="'/my_module/static/src/img/' + image_filename"
alt="Dynamic static image"/>
<!-- Method C: using report_logo (Odoo built-in for PDF headers) -->
<!-- In ir.actions.report, set print_report_name and use the
built-in 'web.base_layout' which already includes the company logo. -->Defining Image Fields in Python Models
from odoo import models, fields
class MyModel(models.Model):
_name = 'my.model'
# fields.Image — recommended for Odoo 13+
# Automatically stores multiple resolution copies (1920/1024/512/256/128)
# when max_width/max_height are set. Stored as base64 in the database.
image = fields.Image(
string='Image',
max_width=1920,
max_height=1920,
)
# fields.Binary with attachment=True — for large files or non-image binaries
# Does NOT auto-generate resized copies.
# Store in filestore (not database column) with attachment=True.
document = fields.Binary(
string='Document',
attachment=True,
)
# For an image that must also have thumbnail variants:
image_1920 = fields.Image('Image', max_width=1920, max_height=1920)
image_128 = fields.Image('Image (128)', related='image_1920',
max_width=128, max_height=128, store=True)Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Broken image icon in PDF report | Using /web/image URL with t-att-src inside a qweb-pdf report — wkhtmltopdf cannot fetch the URL | Replace t-att-src with t-field and t-options="{'widget': 'image'}" on the binary/image field. |
| Image renders in browser but not in PDF | Same cause as above — t-att-src works in browser, fails in PDF | Use t-field with widget='image' for PDF reports. Keep t-att-src for web/portal templates. |
| /web/image returns 404 | The record ID or field name in the URL is wrong, or the user has no read access to the record | Check the model name, record ID, and field name. Confirm the user's record rules allow reading the record. In dev mode, test the URL directly in the browser. |
| Image appears but is very low quality / too small | Using image_128 or image_512 field when the display size requires a larger resolution | Use image_1920 for large display areas and downscale with CSS max-width. Or use the /web/image resize suffix: /web/image/model/id/field/400x300. |
| t-field widget='image' shows raw base64 text instead of an image | The field type is fields.Char or fields.Text, not fields.Binary or fields.Image | Change the field type to fields.Image or fields.Binary. t-field widget='image' only works with Binary/Image field types. |
| Image in portal template shows for admin but not for portal user | Record rule on the model is blocking the portal user from reading the record that holds the image | Check record rules on the model. The /web/image controller respects access rights — if the user can't read the record, they can't see the image. Add a portal-user record rule or use sudo() carefully in a custom controller. |
Version Notes
| Odoo version | Key changes affecting image rendering |
|---|---|
| Odoo 15 | fields.Image stable. /web/image controller and t-field widget='image' behaviour unchanged from Odoo 13/14. wkhtmltopdf still used for PDF generation. |
| Odoo 16 | No breaking changes to image rendering. Odoo 16 adds WebP support to fields.Image (stored as WebP when the source is WebP). /web/image serves WebP to browsers that support it. |
| Odoo 17 | No breaking changes. t-field widget='image' unchanged. /web/image unchanged. PDF generation still uses wkhtmltopdf. Odoo 17 improves image caching headers from /web/image. |
| Odoo 18 & 19 | No breaking changes to image rendering. fields.Image, /web/image, and t-field widget='image' all behave as documented. wkhtmltopdf still required for PDF. |
Need Custom QWeb Reports or Portal Templates for Your Odoo System?
Our certified Odoo developers build custom PDF reports, customer portals, and branded document templates — including ZATCA-compliant e-invoice PDF layouts and Arabic RTL report designs for Saudi businesses.
Frequently Asked Questions
How do I display an image stored in an Odoo model field in a QWeb template?
Why does my image show in the browser but appear broken in the PDF report?
What is the /web/image URL format in Odoo?
What is the difference between fields.Image and fields.Binary in Odoo?
How do I show a company logo in an Odoo QWeb PDF report?
Can I resize images using the /web/image controller?

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
Custom Paper Format in Odoo QWeb Reports
How to create, configure, and bind custom paper formats to Odoo QWeb reports — covering UI setup, XML override, margin control, and header/footer sizing.
How to Show a Progress Bar in Odoo Kanban View (Odoo 16–19)
A complete developer reference for the Odoo Kanban <progressbar> element: field_value, colors, sum_field attributes, selection field setup, and 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.
Explore Related Solutions
Financial 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.
ExploreCRM & 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.
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