File Upload in Web Forms Odoo 18
By Braincuber Team
Published on December 29, 2025
Developer building job application portal creates submission disaster: candidates completing application form need to attach resume PDF but form lacks file upload field forcing applicants to email resumes separately disconnecting applications from attachments requiring 4 hours daily manual matching 200 applications to 200 emailed resumes, uploaded files via email lost in spam folders causing 30 percent application incompleteness, no file type validation allowing candidates uploading executable viruses creating security risks, attachments not linked to database records preventing automated processing and ATS integration, and manual file handling consuming recruiter time reducing productivity 50 percent—creating operational inefficiency security vulnerabilities and poor candidate experience from missing web form file upload capability without automated attachment management.
Odoo 18 Web Form File Upload enables document submission through ir.attachment model integration, Many2many field relationship storing multiple attachments per record, multipart form data encoding preserving file integrity during transmission, file type restriction via accept attribute preventing unwanted formats, CSRF token protection securing against cross-site attacks, base64 encoding for binary storage in database, automatic record linkage through res_model and res_id parameters, and controller-based processing handling upload validation and storage—reducing manual processing 90 percent through automated attachment management eliminating email-based submissions preventing security vulnerabilities enabling seamless document collection through integrated web form file upload achieving complete application data in single submission.
File Upload Features: ir.attachment model, Many2many fields, Multipart encoding, File type restriction, CSRF protection, Base64 encoding, Record linkage, Controller processing, Security validation
Model Configuration
Add Many2many field to model for storing attachments:
attachment_ids = fields.Many2many(
'ir.attachment',
string='Attachments'
)Field Explanation:
- ir.attachment: Odoo predefined model managing all file attachments
- Many2many: Allows multiple files per record
- string: Field label displayed in form views
XML Form Template
Create web form with file upload capability:
<form action="/form/submit" method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token"
t-att-value="request.csrf_token()"/>
<div class="form-group col-md-6">
<label for="att">Attach File</label>
<input type="file"
name="att"
class="form-control"
accept="image/*,application/pdf,video/*"/>
</div>
<div class="form-group mt-3">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>Form Attributes Breakdown:
action="/form/submit"
URL endpoint receiving form submission. Must match controller route.
method="post"
HTTP POST method for submitting data securely.
enctype="multipart/form-data"
Critical for file uploads. Tells browser not to encode file content enabling binary data transmission. Without this uploads fail.
CSRF Token:
Security Essential: Protects against Cross-Site Request Forgery attacks.
<input type="hidden" name="csrf_token"
t-att-value="request.csrf_token()"/>Validates form submission originated from legitimate source not malicious third-party
File Input Configuration:
name="att"
Field identifier used in controller to retrieve uploaded file via kw.get att
accept Attribute
Restricts allowed file types:
- image/*: All image formats (JPEG, PNG, GIF, etc.)
- application/pdf: PDF documents only
- video/*: All video formats (MP4, AVI, MOV, etc.)
Example custom: accept=".pdf,.docx,.xlsx" for specific extensions
Controller Implementation
Handle file upload processing and storage:
from odoo import http
from odoo.http import request
import base64
@http.route(['/form/submit'], type='http', auth='public', website=True)
def file_upload(self, redirect=None, **kw):
current_partner = request.env.user.partner_id
uploaded_file = kw.get('att')
if uploaded_file:
file_name = uploaded_file.filename
file_content = uploaded_file.read()
attachment = request.env['ir.attachment'].sudo().create({
'name': file_name,
'type': 'binary',
'datas': base64.b64encode(file_content),
'res_model': 'res.partner',
'res_id': current_partner.id,
})
current_partner.sudo().write({
'attachment_ids': [(4, attachment.id)],
})
return request.redirect(redirect or '/')Controller Code Breakdown:
Route Decorator:
@http.route(['/form/submit'], type='http', auth='public', website=True)- type='http': Standard HTTP request/response
- auth='public': No login required (adjust for private forms)
- website=True: Website-enabled route
Retrieve Uploaded File:
uploaded_file = kw.get('att')Gets file object from keyword arguments using input name attribute
Extract File Data:
file_name = uploaded_file.filename
file_content = uploaded_file.read()Retrieves filename and reads binary content
Create Attachment Record:
attachment = request.env['ir.attachment'].sudo().create({
'name': file_name,
'type': 'binary',
'datas': base64.b64encode(file_content),
'res_model': 'res.partner',
'res_id': current_partner.id,
})- name: Original filename
- type: binary for file storage vs URL for links
- datas: Base64 encoded content for database storage
- res_model: Model name attachment linked to
- res_id: Specific record ID attachment belongs to
Link Attachment to Record:
current_partner.sudo().write({
'attachment_ids': [(4, attachment.id)],
})ORM Command (4, id): Links existing record to Many2many field without creating duplicate
Complete Example Job Application Form
Template:
<template id="job_application_form">
<form action="/job/apply" method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token"
t-att-value="request.csrf_token()"/>
<div class="form-group">
<label>Full Name</label>
<input type="text" name="name" class="form-control" required=""/>
</div>
<div class="form-group">
<label>Email</label>
<input type="email" name="email" class="form-control" required=""/>
</div>
<div class="form-group">
<label>Resume (PDF only)</label>
<input type="file" name="resume"
accept="application/pdf" required=""/>
</div>
<div class="form-group">
<label>Cover Letter (Optional)</label>
<input type="file" name="cover_letter"
accept="application/pdf,.doc,.docx"/>
</div>
<button type="submit" class="btn btn-primary">Apply Now</button>
</form>
</template>Controller:
@http.route(['/job/apply'], type='http', auth='public', website=True)
def job_application(self, **kw):
# Create applicant record
applicant = request.env['hr.applicant'].sudo().create({
'name': kw.get('name'),
'email_from': kw.get('email'),
})
# Handle resume
resume = kw.get('resume')
if resume:
request.env['ir.attachment'].sudo().create({
'name': resume.filename,
'type': 'binary',
'datas': base64.b64encode(resume.read()),
'res_model': 'hr.applicant',
'res_id': applicant.id,
})
# Handle cover letter if provided
cover = kw.get('cover_letter')
if cover:
request.env['ir.attachment'].sudo().create({
'name': cover.filename,
'type': 'binary',
'datas': base64.b64encode(cover.read()),
'res_model': 'hr.applicant',
'res_id': applicant.id,
})
return request.redirect('/job/thanks')File Type Validation
Frontend and backend validation:
Frontend (HTML):
<input type="file" name="document"
accept=".pdf,.docx,.xlsx"
onchange="validateFileType(this)"/>
<script>
function validateFileType(input) {
const file = input.files[0];
const validTypes = ['application/pdf',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
if (!validTypes.includes(file.type)) {
alert('Invalid file type. Please upload PDF, DOCX, or XLSX only.');
input.value = '';
}
}
</script>Backend (Python):
uploaded_file = kw.get('document')
if uploaded_file:
allowed_extensions = ['.pdf', '.docx', '.xlsx']
file_ext = os.path.splitext(uploaded_file.filename)[1].lower()
if file_ext not in allowed_extensions:
return request.render('website.error', {
'error_message': 'Invalid file type. PDF, DOCX, or XLSX only.'
})Best Practices
Always Validate File Size Backend: Frontend validation bypassed easily by malicious users. 50MB file upload crashes server consuming resources. Backend validation essential checking file size before processing rejecting oversized files. Example: if len file_content greater than 10485760 return error. Protects server resources prevents denial of service.
Store Sensitive Files with Access Rights: Public ir.attachment accessible by anyone knowing URL creating security breach. Confidential documents like contracts or financial statements require access control. Set res_model and res_id linking attachment to protected record inheriting record security rules. Ensures only authorized users access files.
Use Sudo Carefully on Public Routes: sudo bypasses all security checks. Public route with sudo allowing anyone creating attachments anywhere equals major vulnerability. Use sudo only when necessary validating user permissions first or restricting attachment model res_model to safe models preventing abuse.
Conclusion
Odoo 18 Web Form File Upload enables document submission through ir.attachment model integration Many2many field relationships multipart form encoding file type restriction CSRF protection and base64 encoding. Reduce manual processing 90 percent through automated attachment management eliminating email-based submissions preventing security vulnerabilities enabling seamless document collection through integrated web form file upload achieving complete application data in single submission with automatic record linkage.
