Skip to content

File Upload and Binary Fields in Odoo: Storage, Display, and Download

DeployMonkey Team · March 23, 2026 11 min read

Binary Fields in Odoo

Odoo stores files using Binary fields, which hold base64-encoded data in the database or as files on disk via ir.attachment. Understanding how binary fields work is essential for building document management, image galleries, file upload features, and report attachments.

Basic Binary Field

class MaintenanceRequest(models.Model):
    _name = 'maintenance.request'

    photo = fields.Binary(string='Photo', attachment=True)
    photo_filename = fields.Char(string='Photo Filename')
    document = fields.Binary(string='Document', attachment=True)
    document_filename = fields.Char(string='Document Filename')

The attachment=True parameter (default since Odoo 12) stores the file as an ir.attachment record on disk instead of in the database. This is critical for performance since large files in the database slow down backups and queries.

Form View Rendering

<group>
  <field name="photo" widget="image"
         options="{'size': [200, 200]}"/>
  <field name="document" filename="document_filename"/>
  <field name="document_filename" invisible="1"/>
</group>

The widget="image" renders binary data as an image preview. Without the widget, binary fields render as a file upload/download button. The filename attribute links to a Char field that stores the original filename for downloads.

Image Fields

Odoo provides dedicated Image fields that automatically resize images:

image_1920 = fields.Image(string='Image', max_width=1920, max_height=1920)
image_256 = fields.Image(
    related='image_1920', max_width=256, max_height=256, store=True)
image_128 = fields.Image(
    related='image_1920', max_width=128, max_height=128, store=True)

The Image field extends Binary with automatic resizing. The pattern above creates multiple resolutions from a single upload: full size (1920px), medium (256px), and thumbnail (128px).

File Upload Widget Options

<!-- Image with preview size -->
<field name="image_1920" widget="image"
       options="{'preview_image': 'image_128',
                 'size': [90, 90]}"/>

<!-- Binary file with accepted types -->
<field name="document" widget="binary"
       filename="document_filename"
       options="{'accepted_file_extensions': '.pdf,.doc,.docx'}"/>

<!-- Multiple files via Many2many -->
<field name="attachment_ids" widget="many2many_binary"
       string="Attachments"/>

Programmatic File Handling

Setting Binary Values

import base64

# From file path
with open('/path/to/file.pdf', 'rb') as f:
    record.document = base64.b64encode(f.read())
    record.document_filename = 'file.pdf'

# From URL
import requests
response = requests.get('https://example.com/image.png')
record.photo = base64.b64encode(response.content)

Reading Binary Values

# Decode to bytes
file_bytes = base64.b64decode(record.document)

# Save to disk
with open('/tmp/output.pdf', 'wb') as f:
    f.write(file_bytes)

# Get file size
size_bytes = len(base64.b64decode(record.document))

Using ir.attachment

The ir.attachment model provides a higher-level file management API:

# Create an attachment
attachment = self.env['ir.attachment'].create({
    'name': 'report.pdf',
    'type': 'binary',
    'datas': base64.b64encode(pdf_content),
    'res_model': self._name,
    'res_id': self.id,
    'mimetype': 'application/pdf',
})

# Search attachments for a record
attachments = self.env['ir.attachment'].search([
    ('res_model', '=', 'maintenance.request'),
    ('res_id', '=', record.id),
])

File Size Validation

Enforce file size limits with constraints:

@api.constrains('document')
def _check_document_size(self):
    max_size = 10 * 1024 * 1024  # 10 MB
    for record in self:
        if record.document:
            file_size = len(base64.b64decode(record.document))
            if file_size > max_size:
                raise ValidationError(
                    'Document size cannot exceed 10 MB.')

Download Controller

Create a controller for file downloads:

from odoo import http
from odoo.http import request, content_disposition
import base64

class FileController(http.Controller):

    @http.route('/download/document/<int:record_id>',
                type='http', auth='user')
    def download_document(self, record_id, **kw):
        record = request.env['maintenance.request'].browse(record_id)
        if not record.exists() or not record.document:
            return request.not_found()

        content = base64.b64decode(record.document)
        filename = record.document_filename or 'document'

        return request.make_response(
            content,
            headers=[
                ('Content-Type', 'application/octet-stream'),
                ('Content-Disposition',
                 content_disposition(filename)),
            ])

Image Handling Best Practices

  • Always use the Image field type for images (not raw Binary) to get automatic resizing
  • Store multiple resolutions: 1920 for full display, 256 for lists, 128 for thumbnails
  • Use preview_image option to load thumbnails instead of full images in form views
  • In kanban views, use kanban_image() to load the smallest available resolution

Storage Considerations

With attachment=True, files are stored in the filestore directory (default: ~/.local/share/Odoo/filestore/DBNAME/). This is important for:

  • Backups: include the filestore directory in your backup strategy
  • Disk space: monitor filestore growth, especially with user-uploaded images
  • Multi-server: use shared storage (NFS, S3) for filestore in clustered deployments

Security

Binary fields respect Odoo's access control. Users can only download files from records they have read access to. For public downloads (e.g., portal), use auth='public' on the controller and implement access token verification.