How to Create Backend Records from Website Forms in Odoo 18
By Braincuber Team
Published on January 19, 2026
Your website visitors fill out a form. That data needs to become a record in Odoo's backend—a lead, a customer, a support ticket, an event registration. This is one of the most common integration points between Odoo's website frontend and its powerful backend models.
This tutorial walks through building a complete website-to-backend form flow in Odoo 18. We'll create a support request form that website visitors can fill out, which creates a helpdesk.ticket-style record in the backend. You'll learn QWeb templates, custom controllers, CSRF protection, and form validation—everything needed for production-ready forms.
Prerequisites: Basic knowledge of Odoo module structure. You'll need a custom module with website as a dependency.
How It Works
1. QWeb Template
HTML form wrapped in Odoo's QWeb syntax. Renders as a website page with Bootstrap styling.
2. Controller
Python class with route decorators. Handles GET (display form) and POST (process submission).
3. Model Create
Controller uses sudo().create() to save form data as a backend record.
4. Success Page
After submission, redirect to a confirmation page or display a success message.
Step 1: Create the Module Structure
Set up your custom module with the proper structure:
website_support_form/
├── __init__.py
├── __manifest__.py
├── controllers/
│ ├── __init__.py
│ └── main.py
├── models/
│ ├── __init__.py
│ └── support_request.py
├── security/
│ └── ir.model.access.csv
└── views/
└── support_form_templates.xml
{
'name': 'Website Support Form',
'version': '18.0.1.0.0',
'summary': 'Create support requests from website',
'depends': ['website'],
'data': [
'security/ir.model.access.csv',
'views/support_form_templates.xml',
],
'installable': True,
'license': 'LGPL-3',
}
Step 2: Create the Model
Define a model to store the form submissions:
from odoo import models, fields
class SupportRequest(models.Model):
_name = 'support.request'
_description = 'Website Support Request'
_order = 'create_date desc'
name = fields.Char(string='Subject', required=True)
email = fields.Char(string='Email', required=True)
phone = fields.Char(string='Phone')
category = fields.Selection([
('technical', 'Technical Issue'),
('billing', 'Billing Question'),
('general', 'General Inquiry'),
], string='Category', default='general')
description = fields.Text(string='Description', required=True)
state = fields.Selection([
('new', 'New'),
('in_progress', 'In Progress'),
('resolved', 'Resolved'),
], string='Status', default='new')
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_support_request_user,support.request.user,model_support_request,base.group_user,1,1,1,1
Step 3: Create the QWeb Template
Build the form using QWeb with Bootstrap styling:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Support Request Form -->
<template id="support_form_page" name="Support Form">
<t t-call="website.layout">
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="card shadow-sm">
<div class="card-body p-4">
<h2 class="text-center mb-4">Submit Support Request</h2>
<form action="/support/submit" method="POST">
<!-- CSRF Token (Required) -->
<input type="hidden" name="csrf_token"
t-att-value="request.csrf_token()"/>
<div class="mb-3">
<label class="form-label">Subject *</label>
<input type="text" name="name"
class="form-control" required="required"
placeholder="Brief description of your issue"/>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Email *</label>
<input type="email" name="email"
class="form-control" required="required"/>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Phone</label>
<input type="tel" name="phone"
class="form-control"/>
</div>
</div>
<div class="mb-3">
<label class="form-label">Category</label>
<select name="category" class="form-select">
<option value="general">General Inquiry</option>
<option value="technical">Technical Issue</option>
<option value="billing">Billing Question</option>
</select>
</div>
<div class="mb-4">
<label class="form-label">Description *</label>
<textarea name="description" class="form-control"
rows="5" required="required"
placeholder="Please describe your issue in detail"></textarea>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg">
Submit Request
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Success Page -->
<template id="support_success_page" name="Support Success">
<t t-call="website.layout">
<div class="container py-5 text-center">
<div class="py-5">
<div class="mb-4">
<span class="text-success" style="font-size: 4rem;">✓</span>
</div>
<h2>Request Submitted Successfully!</h2>
<p class="text-muted">We've received your support request and will respond within 24 hours.</p>
<a href="/" class="btn btn-outline-primary mt-3">Return to Home</a>
</div>
</div>
</t>
</template>
<!-- Website Menu Item -->
<record id="menu_support_form" model="website.menu">
<field name="name">Support</field>
<field name="url">/support</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">50</field>
</record>
</odoo>
Step 4: Create the Controller
Handle form display and submission:
from odoo import http
from odoo.http import request
class SupportFormController(http.Controller):
@http.route('/support', type='http', auth='public', website=True)
def support_form(self, **kwargs):
"""Display the support request form"""
return request.render('website_support_form.support_form_page')
@http.route('/support/submit', type='http', auth='public',
methods=['POST'], website=True, csrf=True)
def support_submit(self, **post):
"""Handle form submission and create backend record"""
# Validate required fields
if not post.get('name') or not post.get('email') or not post.get('description'):
return request.redirect('/support?error=missing_fields')
# Create the support request record
request.env['support.request'].sudo().create({
'name': post.get('name'),
'email': post.get('email'),
'phone': post.get('phone', ''),
'category': post.get('category', 'general'),
'description': post.get('description'),
})
# Redirect to success page
return request.render('website_support_form.support_success_page')
Security Notes:
csrf=Trueenables CSRF protection—always include the hidden token in formssudo()bypasses access rights since public users can't create records directly- Always validate input before creating records
Key Controller Decorators
| Parameter | Purpose |
|---|---|
| type='http' | Standard HTTP request (vs 'json' for AJAX) |
| auth='public' | Accessible without login |
| website=True | Load website context and templates |
| methods=['POST'] | Accept only POST requests |
| csrf=True | Require valid CSRF token |
Testing Your Form
Install the Module
Go to Apps, click Update App List, search for your module, and install it.
Visit the Form Page
Navigate to yoursite.com/support or click the "Support" menu item.
Submit Test Data
Fill out the form and submit. Check the backend for the new record.
Result: The form submission creates a new support.request record visible in the backend. You can build list and form views to manage these records.
Conclusion
Creating backend records from website forms is a fundamental Odoo pattern. The combination of QWeb templates, custom controllers, and ORM methods gives you complete control over what data is collected and how it's stored. This pattern works for contact forms, lead capture, event registration, surveys, and any other website-to-backend integration.
Key Takeaway: QWeb template displays form → Controller handles GET and POST → sudo().create() saves to model → Redirect to success page. Always include CSRF token and validate input.
