Skip to content

Odoo Web Controller Patterns and Mixins: HTTP and JSON-RPC Endpoints

DeployMonkey Team · March 23, 2026 12 min read

Odoo Controller Basics

Controllers handle HTTP requests in Odoo. They map URL routes to Python methods that return responses: HTML pages, JSON data, file downloads, or redirects. Understanding controllers is essential for building web pages, APIs, and portal features.

Basic HTTP Controller

from odoo import http
from odoo.http import request


class MyController(http.Controller):

    @http.route('/my/page', type='http', auth='user', website=True)
    def my_page(self, **kwargs):
        records = request.env['my.model'].search([])
        return request.render('my_module.my_template', {
            'records': records,
        })

Route Decorator Parameters

The @http.route() decorator configures endpoint behavior:

@http.route(
    '/api/tickets',          # URL path
    type='http',             # 'http' or 'json'
    auth='user',             # 'none', 'public', 'user'
    methods=['GET', 'POST'], # allowed HTTP methods
    csrf=True,               # CSRF protection (default True for http)
    website=True,            # use website layout
    sitemap=False,           # include in sitemap
    cors='*',                # CORS header
)
def api_tickets(self, **kwargs):
    pass

Authentication Modes

  • auth='none': no authentication, no database access. For health checks and static endpoints.
  • auth='public': allows both authenticated and anonymous users. Use request.env.user to check if logged in.
  • auth='user': requires login. Redirects to login page if not authenticated.

Request Types

  • type='http': standard HTTP. Receives form data, query params. Returns HTML, files, redirects.
  • type='json': JSON-RPC. Receives/returns JSON. Automatically parses request body and wraps response.

HTTP Response Types

# Render a QWeb template
return request.render('module.template_id', values)

# Redirect
return request.redirect('/my/other-page')

# Return raw response
return request.make_response(
    'Hello World',
    headers=[('Content-Type', 'text/plain')])

# JSON response (from type='http' route)
import json
return request.make_response(
    json.dumps({'status': 'ok'}),
    headers=[('Content-Type', 'application/json')])

# File download
return request.make_response(
    file_content,
    headers=[
        ('Content-Type', 'application/octet-stream'),
        ('Content-Disposition', 'attachment; filename="report.pdf"'),
    ])

# 404
return request.not_found()

JSON-RPC Controllers

JSON-RPC endpoints automatically parse JSON requests and wrap responses:

@http.route('/api/tickets/create', type='json', auth='user')
def create_ticket(self, name, description, priority='1'):
    ticket = request.env['support.ticket'].create({
        'name': name,
        'description': description,
        'priority': priority,
    })
    return {'id': ticket.id, 'name': ticket.name}

JSON-RPC requests must use POST with Content-Type: application/json and a body like:

{"jsonrpc": "2.0", "method": "call",
 "params": {"name": "Bug", "description": "Details"}}

REST-like API Pattern

Build REST-style endpoints using type='http' with explicit JSON handling:

import json

class TicketAPI(http.Controller):

    @http.route('/api/v1/tickets', type='http',
                auth='user', methods=['GET'], csrf=False)
    def list_tickets(self, **kwargs):
        tickets = request.env['support.ticket'].search_read(
            [], ['name', 'state', 'priority'], limit=50)
        return request.make_response(
            json.dumps({'data': tickets}),
            headers=[('Content-Type', 'application/json')])

    @http.route('/api/v1/tickets', type='http',
                auth='user', methods=['POST'], csrf=False)
    def create_ticket(self, **kwargs):
        data = json.loads(request.httprequest.data)
        ticket = request.env['support.ticket'].create({
            'name': data['name'],
            'description': data.get('description', ''),
        })
        return request.make_response(
            json.dumps({'id': ticket.id}),
            headers=[('Content-Type', 'application/json')])

    @http.route('/api/v1/tickets/<int:ticket_id>',
                type='http', auth='user',
                methods=['GET'], csrf=False)
    def get_ticket(self, ticket_id, **kwargs):
        ticket = request.env['support.ticket'].browse(ticket_id)
        if not ticket.exists():
            return request.make_response(
                json.dumps({'error': 'Not found'}),
                status=404,
                headers=[('Content-Type', 'application/json')])
        return request.make_response(
            json.dumps(ticket.read(['name', 'state'])[0]),
            headers=[('Content-Type', 'application/json')])

Controller Inheritance

Extend existing controllers by inheriting from the original class:

from odoo.addons.website_sale.controllers.main import WebsiteSale


class WebsiteSaleCustom(WebsiteSale):

    @http.route()  # keep original route
    def shop(self, **kwargs):
        response = super().shop(**kwargs)
        # Modify the response
        response.qcontext['custom_data'] = 'value'
        return response

Use @http.route() without arguments to keep the parent's route configuration. Override with new arguments to change the route.

Request Object

The request object provides access to:

# Database environment (ORM)
request.env['model.name'].search([])

# Current user
request.env.user
request.uid

# HTTP request details
request.httprequest.method      # GET, POST, etc.
request.httprequest.path         # /my/page
request.httprequest.args         # query parameters
request.httprequest.form         # form data
request.httprequest.data         # raw body bytes
request.httprequest.headers      # request headers
request.httprequest.remote_addr  # client IP

# Session
request.session.sid              # session ID
request.session.uid              # user ID

# Database cursor
request.env.cr                   # for raw SQL

CSRF Protection

Odoo enables CSRF protection by default for type='http' POST routes. To handle this:

<!-- In forms, include the CSRF token -->
<form method="POST" action="/my/endpoint">
  <input type="hidden" name="csrf_token"
         t-att-value="request.csrf_token()"/>
  <!-- form fields -->
</form>

# Or disable CSRF for API endpoints
@http.route('/api/endpoint', csrf=False)

Error Handling

from odoo.exceptions import AccessError, UserError
from werkzeug.exceptions import NotFound, Forbidden

@http.route('/api/resource/<int:res_id>', type='http', auth='user')
def get_resource(self, res_id, **kwargs):
    try:
        record = request.env['my.model'].browse(res_id)
        if not record.exists():
            raise NotFound('Resource not found')
        data = record.read(['name', 'value'])
        return request.make_response(
            json.dumps(data), headers=[...])
    except AccessError:
        raise Forbidden('Access denied')
    except Exception as e:
        _logger.exception('API error')
        return request.make_response(
            json.dumps({'error': str(e)}), status=500,
            headers=[('Content-Type', 'application/json')])