In Odoo, URLs act as entry points that connect users to specific functionalities, whether that’s loading a webpage, fetching data through an API, or handling a form submission. Behind the scenes, routing defines how these URLs are processed and which controller should respond.
Odoo 19, like its earlier versions, uses a structured approach where Python controllers and the @http.route decorator play the central role. This mechanism allows developers to map requests to the right logic, apply access rules, and decide how the response should be delivered—whether as an HTML page or as JSON data for frontend applications.
A solid understanding of URLs and routing ensures that developers can build clean, secure, and maintainable modules in Odoo. In this blog, we’ll explore the core principles of routing in Odoo 19, look at examples, and highlight the different ways requests can be handled efficiently.
Controllers & the @http.route Decorator
In Odoo, controllers are Python classes that handle incoming web requests. They inherit from http.Controller and use the @http.route decorator to define how a particular URL should behave. This combination decides:
- Which URL path the request will match
- What type of response is expected (http or json)
- Who can access it (via auth rules)
- Whether it’s part of the website or backend
Here’s a basic example:
from odoo import http
from odoo.http import request
class MyFirstController(http.Controller):
@http.route('/hello', type='http', auth='public', website=True)
def hello_world(self, **kwargs):
return "Hello, Odoo 19!"
Understanding the Route Parameters
When defining a route in Odoo using the @http.route decorator, each parameter serves a specific purpose. Here’s what they mean in practice:
- /hello: This is the URL path. When a user visits /hello in their browser, Odoo knows which controller method should be triggered.
- type='http': Specifies the response type. With http, the route returns standard HTTP content like HTML pages or plain text.
- auth='public': Determines who can access the route. In this case, it’s open to everyone, even without logging into Odoo.
- website=True: Declares that this is a website route, making it part of Odoo’s website module. These routes often render QWeb templates for frontend pages.
- hello_world(): This is the method inside the controller that executes whenever the route is accessed. It contains the logic for the response.
Together, these parameters give developers full control over how a request is handled—from the URL itself to the type of output and the level of access control.
Returning a Template
In most real-world scenarios, we don’t want to send plain text back to the browser. Instead, we render QWeb templates, which allow us to build dynamic and styled webpages. Odoo makes this simple through the request.render() method.
Here’s an example:
@http.route('/hello/template', type='http', auth='public', website=True)
def hello_template(self, **kwargs):
return request.render('my_module.hello_template', {'name': 'Odoo'})
The request.render method instructs Odoo to load and display a specific QWeb template—in this case, hello_template from the module my_module. Along with the template, we can pass dynamic values using a dictionary, such as {'name': 'Odoo'}, which can then be displayed within the page using QWeb expressions.
This setup reflects the standard design pattern in Odoo website development, where the controller is responsible for fetching data and handling logic, while the QWeb template manages the presentation layer. By keeping these responsibilities separate, developers ensure that business logic and frontend design remain cleanly divided, making applications easier to maintain and extend in the long run.
Types of Routes in Odoo 19
Routing in Odoo is designed to be flexible because not all requests are the same. Sometimes you need to display a full webpage to users, and other times you simply want to send back raw data for an AJAX call or API integration. To handle these different needs, Odoo provides two main route types: HTTP routes and JSON routes. Each has its own use case, and understanding the difference helps developers choose the right approach.
HTTP Routes
An HTTP route is the most common type. It is typically used to serve webpages, render QWeb templates, or even return plain text responses. These routes are often tied to Odoo’s website module, but can also be used in backend modules where a standard HTTP response is needed.
Example:
@http.route('/product/list', type='http', auth='public', website=True)
def product_list(self, **kwargs):
products = request.env['product.template'].search([])
return request.render('my_module.product_page', {'products': products})
Here, /product/list is an HTTP route that fetches products from the database and renders them in a QWeb template. This is the go-to pattern for building website pages.
JSON Routes
A JSON route is different in that it returns structured JSON data instead of HTML. These routes are particularly useful for AJAX calls, JavaScript integrations, or when building lightweight APIs within Odoo.
Example:
@http.route('/api/products', type='json', auth='user')
def product_api(self, **kwargs):
products = request.env['product.template'].search([])
return [{'id': p.id, 'name': p.name} for p in products]
In this case, /api/products provides product data in JSON format, which can then be consumed by frontend code or third-party applications. This makes JSON routes the preferred option when working with data-driven interactions.
By choosing the right type of route, developers can build Odoo modules that are efficient, secure, and maintainable.
Dynamic Routes and Parameters
In many cases, a static URL is not enough. Imagine building a product catalog—you don’t want to create a separate route for every product. Instead, you can define dynamic routes that capture values directly from the URL and pass them into your controller method. This allows you to handle multiple records or actions with a single route definition.
Integer Parameters
The most common use case is to capture an integer ID from the URL.
@http.route('/product/<int:product_id>', type='http', auth='public', website=True)
def product_page(self, product_id):
product = request.env['product.template'].browse(product_id)
return request.render('my_module.product_detail', {'product': product})
Here, if a user visits /product/10, the value 10 is passed as product_id, and the corresponding product record is loaded and displayed.
String Parameters
You can also capture string values from the URL, often used for slugs or identifiers.
@http.route('/blog/<string:slug>', type='http', auth='public', website=True)
def blog_post(self, slug):
post = request.env['blog.post'].search([('slug', '=', slug)], limit=1)
return request.render('my_module.blog_detail', {'post': post})
This approach is useful for building SEO-friendly URLs like /blog/odoo-guide.
Model Parameters
Odoo takes it one step further by allowing you to capture a parameter as a model record directly.
@http.route('/customer/<model("res.partner"):partner>', type='http', auth='user')
def customer_detail(self, partner):
return request.render('my_module.customer_page', {'partner': partner})
In this example, visiting /customer/5 will automatically resolve to the res.partner record with ID 5. This is both clean and efficient, since you don’t need to manually browse the record inside the method.
Dynamic routes make your Odoo applications flexible and user-friendly. With just one route definition, you can handle a wide range of URLs and ensure that your pages scale cleanly as your data grows.
Authentication Modes in Routes
Every route in Odoo requires an authentication level, which defines who can access it. Choosing the right mode ensures both usability and security.
- auth='public': The route is accessible to anyone, even without logging in. This is commonly used for website pages like product catalogs or blog posts.
- auth='user': Only logged-in users can access the route. Ideal for employee dashboards, portal pages, or any feature tied to a specific user account.
- auth='none': Skips Odoo’s authentication checks entirely. This is mainly used for raw API endpoints or system integrations. Since it bypasses all login rules, it should be handled with caution.
In short, use public for general website visitors, user for authenticated access, and reserve none for very specific technical cases. Picking the right mode balances convenience with proper security.
A Practical Example: Displaying Sale Orders
Now that we’ve explored the concepts of routes, parameters, and authentication, let’s put it all together with a practical example. Suppose we want to display a list of sale orders on a webpage using a custom controller and a QWeb template.
Step 1: Define the Controller
We create a controller in our module under controllers/main.py:
from odoo import http
from odoo.http import request
class SaleOrderController(http.Controller):
@http.route('/my/sale_orders', type='http', auth='user', website=True)
def list_sale_orders(self, **kwargs):
orders = request.env['sale.order'].sudo().search([])
return request.render('my_module.sale_order_template', {
'orders': orders,
})
- @http.route('/my/sale_orders'): The URL to access sale orders.
- auth='user': Requires the user to log in.
- sudo(): Ensures the controller can fetch records even if the user has limited permissions.
- request.render(): Renders a QWeb template and passes the fetched data.
Step 2: Create the QWeb Template
In views/sale_order_template.xml:
<template id="sale_order_template" name="Sale Orders Page">
<t t-call="website.layout">
<div class="container">
<h2>My Sale Orders</h2>
<ul>
<t t-foreach="orders" t-as="order">
<li>
<strong><t t-esc="order.name"/></strong> -
<t t-esc="order.partner_id.name"/> -
<t t-esc="order.amount_total"/>
</li>
</t>
</ul>
</div>
</t>
</template>
Here:
- t-foreach="orders": Loops through all sale orders passed from the controller.
- t-esc: Safely displays field values like order number, customer, and amount.
- website.layout: Inherits Odoo’s standard website layout (header, footer, etc.).
After updating your module, visit /my/sale_orders in the browser. If logged in, you’ll see a neatly listed set of sale orders pulled directly from Odoo.
This example demonstrates how routes, authentication, controllers, and templates come together as the core building blocks of Odoo’s web development framework.
Conclusion
Routing in Odoo is much more than just defining URLs—it’s the foundation that connects user requests with business logic and presentation. We explored how controllers use the @http.route decorator, the difference between HTTP and JSON routes, handling dynamic parameters, and applying the right authentication modes. Finally, with a practical example, we saw how all these elements work together to build a functional webpage.
By understanding and applying these concepts, developers can create secure, flexible, and user-friendly routes in Odoo 19. Whether it’s powering website pages, APIs, or custom dashboards, mastering routing ensures that your Odoo applications are both robust and easy to extend.
To read more about What is URLS & Routing in Odoo 18, refer to our blog What is URLS & Routing in Odoo 18