Odoo Development

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.

iWesabe Editorial TeamSeptember 1, 20208 min read

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

QWeb image rendering methods by context
MethodTemplate contextImage sourceNotes
/web/image URL + t-att-srcWebsite, portal, web views, email (external send)Binary/Image field on any modelMost flexible. Odoo serves the image as HTTP. Supports resize parameters.
t-field widget='image'PDF reports (qweb-pdf) onlyBinary/Image fieldRenders 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 templateFile in module's static/src/ directoryFor module-bundled images (logos, icons). Path: /web/static/src/<module>/img/<file>.
Inline base64 (t-att-src with data URI)PDF reports, email templatesBase64 string computed in PythonFallback 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

text
# 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/logo

t-att-src Patterns in QWeb Templates

xml
<!-- 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.

xml
<!-- 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.

xml
<!-- 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

python
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

Common QWeb image problems and fixes
SymptomLikely causeFix
Broken image icon in PDF reportUsing /web/image URL with t-att-src inside a qweb-pdf report — wkhtmltopdf cannot fetch the URLReplace t-att-src with t-field and t-options="{'widget': 'image'}" on the binary/image field.
Image renders in browser but not in PDFSame cause as above — t-att-src works in browser, fails in PDFUse t-field with widget='image' for PDF reports. Keep t-att-src for web/portal templates.
/web/image returns 404The record ID or field name in the URL is wrong, or the user has no read access to the recordCheck 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 smallUsing image_128 or image_512 field when the display size requires a larger resolutionUse 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 imageThe field type is fields.Char or fields.Text, not fields.Binary or fields.ImageChange 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 userRecord rule on the model is blocking the portal user from reading the record that holds the imageCheck 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

QWeb image rendering changes by Odoo version
Odoo versionKey changes affecting image rendering
Odoo 15fields.Image stable. /web/image controller and t-field widget='image' behaviour unchanged from Odoo 13/14. wkhtmltopdf still used for PDF generation.
Odoo 16No 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 17No 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 & 19No 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.

WhatsApp

Frequently Asked Questions

How do I display an image stored in an Odoo model field in a QWeb template?
Use the `/web/image` controller with `t-att-src` for web and portal templates: ``. For PDF reports, use `t-field` instead: ``. Never use the `/web/image` URL in PDF reports — wkhtmltopdf cannot fetch HTTP URLs from the Odoo server.
Why does my image show in the browser but appear broken in the PDF report?
The most common cause is using `t-att-src` with a `/web/image` URL in a PDF report template. The browser can fetch that URL, but wkhtmltopdf (which generates the PDF) runs as a separate process without network access to the Odoo server. Fix: replace `t-att-src` with `t-field` and add `t-options="{'widget': 'image'}"`. This tells Odoo to embed the image as a base64 data URI directly in the HTML before wkhtmltopdf processes it.
What is the /web/image URL format in Odoo?
The basic format is `/web/image///`. For example: `/web/image/res.partner/42/image_1920`. You can add an optional resize suffix: `/web/image/res.partner/42/image_1920/128x128` — Odoo resizes the image server-side and caches the result. This URL respects Odoo's access control — users without read access to the record will receive a 404 or a placeholder image depending on the model's configuration.
What is the difference between fields.Image and fields.Binary in Odoo?
`fields.Image` (Odoo 13+) is a specialised subclass of `fields.Binary` designed for images. It validates the file as a recognised image format (PNG/JPEG/GIF/WebP/SVG), can automatically store multiple resized copies when `max_width`/`max_height` are set, and works with the Odoo image widget in both form views and QWeb. `fields.Binary` is a generic binary field with no MIME validation or auto-resize — suitable for documents, spreadsheets, or any arbitrary file. Both render the same way in QWeb: `/web/image` URL or `t-field widget='image'`.
How do I show a company logo in an Odoo QWeb PDF report?
Use `t-field` on the company's `logo` field: ``. If you inherit from the standard `web.external_layout` or `web.basic_layout`, the company logo is already included in the header — you do not need to add it manually. If you build a custom layout from scratch, use the `t-field` approach above.
Can I resize images using the /web/image controller?
Yes. Append `/x` to the URL: `/web/image/product.product/7/image_1920/200x200`. Odoo resizes the image server-side using PIL, caches the result, and returns the resized version. If you specify only width (e.g. `/200x0`) or only height (`/0x200`), Odoo scales proportionally. This server-side resize is more reliable than CSS resizing because it reduces the actual bytes transferred — especially useful for product thumbnails in list views.
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