Master Odoo OWL Public Components: A Powerful 7-Step Tutorial for Dynamic Web Experiences
Welcome, Odoo developers! Are you ready to elevate your Odoo website and portal interfaces beyond standard functionalities? Do you want to build truly interactive, dynamic, and public-facing web experiences? Then mastering Odoo OWL public components is your next essential step. This comprehensive guide, inspired by the insights from the Odoo OWL Live MasterClass - Exploring New Public Components Part 2 (watch the full session here: https://www.youtube.com/watch?v=49YcoZNfw5M), will walk you through the entire process, from module setup to deployment, ensuring you unlock the full power of OWL in a public context.
The Power of Odoo OWL Public Components
In the world of Odoo development, creating engaging and responsive user interfaces is paramount. While Odoo’s core functionalities are robust, custom requirements often demand more dynamic and interactive elements. This is where Odoo OWL public components come into play. OWL (Odoo Web Library) is Odoo’s modern, reactive JavaScript framework, and when combined with the “public” access setting, it allows you to build sophisticated components that can be used by anyone visiting your Odoo website or portal, even unauthenticated users.
Imagine building a custom dashboard for your website visitors, a dynamic course catalog, or an interactive form that fetches real-time data from your Odoo backend. All these and more are perfectly achievable with Odoo OWL public components. They empower you to extend Odoo’s frontend capabilities significantly, offering a fluid and modern user experience.
Why Mastering Odoo OWL Public Components is Crucial
Adopting OWL components for public-facing modules offers several compelling advantages:
- Enhanced User Experience: Create highly interactive and responsive interfaces that improve user engagement and satisfaction.
- Modern Web Development: Leverage a reactive framework that aligns with current web development best practices, making your code cleaner and more maintainable.
- Seamless Integration: OWL components integrate natively with Odoo’s QWeb templating engine and backend services, providing a cohesive development experience.
- Scalability and Reusability: Design modular components that can be easily reused across different parts of your website or portal, promoting scalability.
- Public Access: Specifically designed for scenarios where data and interactions are needed for unauthenticated users, expanding the reach and utility of your Odoo applications.
This tutorial will not only show you how to implement these components but also provide crucial tips and best practices to avoid common pitfalls, making your development journey smoother and more efficient.
Setting the Stage: Prerequisites & Environment
Before we dive into the steps, ensure you have the following:
- Basic Odoo Development Knowledge: Familiarity with Odoo module structure, Python, JavaScript, and XML.
- Odoo Version: Odoo 16 or higher is recommended to take full advantage of the latest OWL features.
- OWL Basics: A fundamental understanding of the OWL framework, including concepts like components, reactive state, and lifecycle hooks. If you’re new to OWL, consider checking out the official Odoo documentation on OWL.
- Odoo Website Module: The
websitemodule should be installed and configured in your Odoo instance. - Development Tools: A good code editor like Visual Studio Code and browser developer tools for debugging.
Let’s get started on building your first powerful Odoo OWL public components!
7-Step Tutorial: Building a Dynamic Course List Component
We’ll create a module that displays a list of courses from the website_slides module on a public portal page. This will demonstrate how to fetch data from the backend using an OWL component.
Step 1: Module Setup and Directory Structure
First, create a new Odoo module. For this example, let’s call it my_public_component_module. The directory structure is crucial for Odoo to correctly load your assets.
my_public_component_module/
├── __init__.py
├── __manifest__.py
├── controllers/
│ ├── __init__.py
│ └── main.py # Python controller for backend data
├── models/
│ └── models.py # You might have Odoo models here, but optional for this example
├── views/
│ └── portal_templates.xml # XML file to inherit and place your component
└── static/
└── src/
├── js/
│ └── components/
│ ├── course_list.js # Your OWL component's JavaScript
│ └── course_list.xml # Your OWL component's QWeb template
└── css/
└── main.css # Optional: for custom styling
Content for my_public_component_module/__init__.py (empty):
# Nothing needed here for this example, or import models/controllers as needed.
Content for my_public_component_module/models/__init__.py (empty):
# Nothing needed here for this example.
Step 2: Module Manifest (__manifest__.py)
This file tells Odoo about your module and its dependencies, and importantly, registers your frontend assets.
{
'name': 'My Public OWL Components',
'version': '1.0',
'summary': 'Demonstrates Odoo OWL Public Components for website/portal',
'description': """
This module provides an example of how to create and integrate
Odoo OWL public components into the Odoo frontend (website/portal)
to display dynamic data.
""",
'author': 'Your Name/Company',
'website': 'https://yourwebsite.com',
'license': 'LGPL-3',
'depends': ['base', 'website', 'website_slides'], # Crucial dependencies
'data': [
'views/portal_templates.xml',
],
'assets': {
'web.assets_frontend': [
'my_public_component_module/static/src/js/components/*.js',
'my_public_component_module/static/src/js/components/*.xml',
'my_public_component_module/static/src/css/*.css', # Include CSS if you have it
],
},
'installable': True,
'application': True,
'auto_install': False,
}
Key Points in the Manifest:
depends: Ensurewebsiteandwebsite_slides(if you are inheriting) are listed. These provide the necessary framework and models.assets: This section is vital.web.assets_frontendensures your JavaScript and XML files are loaded on the website. The wildcards*.jsand*.xmlconveniently include all files in those directories.
Step 3: Creating the Controller (controllers/main.py)
Our Python controller will serve the course data to our frontend OWL component.
Content for my_public_component_module/controllers/__init__.py:
from . import main
Content for my_public_component_module/controllers/main.py:
from odoo import http
from odoo.http import route, request
import json # Important for JSON responses
class PublicCourseController(http.Controller):
@route('/my_portal/get_public_courses', type='json', auth='public', website=True)
def get_public_courses(self, **kwargs):
"""
Retrieves a list of public courses.
This endpoint is accessible to unauthenticated users.
"""
# A partner_id might be passed if we want to filter by user subscriptions,
# but for a purely public component, it might not be strictly necessary.
# However, the context mentions passing partner_id, so we'll allow it.
partner_id = kwargs.get('partner_id')
domain = [('website_published', '=', True)]
if partner_id:
# Example: Fetch courses subscribed by a specific partner if needed.
# For this example, we'll just show all public courses.
# In a real scenario, you might filter 'slide.channel' subscriptions.
pass # Or adjust domain, e.g., domain.append(('partner_ids', 'in', int(partner_id)))
# Fetch published slides (courses)
slides = request.env['slide.slide'].sudo().search(domain)
courses_data = []
for slide in slides:
courses_data.append({
'id': slide.id,
'name': slide.name,
'description': slide.description,
'url': slide.website_url, # Link to the course page
})
# Return the data in JSON format
return courses_data
Controller Explained:
@routeDecorator:'/my_portal/get_public_courses': The URL path for this endpoint.type='json': Specifies that this route expects and returns JSON data.auth='public': Crucial forOdoo OWL public components. This allows any user, authenticated or not, to access this route.website=True: Marks this route as being part of the website, which can affect things like website layout rendering (though not directly for a JSON endpoint).
get_public_coursesMethod:- Uses
request.env['slide.slide'].sudo().search(domain)to fetch publicly published courses.sudo()is used to bypass access rights if the public user doesn’t have direct access, which is often the case for public endpoints. - The results are formatted into a list of dictionaries, which Python’s
jsonlibrary handles perfectly for thetype='json'route.
- Uses
Step 4: Creating the OWL Component (static/src/js/components/course_list.js)
This JavaScript file defines your OWL component, manages its state, and communicates with the backend.
/** @odoo-module **/
import { Component, useState, onMounted } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry"; // Important for public components
export class PublicCourseList extends Component {
setup() {
this.state = useState({
courses: [],
isLoading: true,
error: null,
totalCourses: 0,
});
this.rpc = useService("rpc"); // Access Odoo's RPC service
// Fetch courses when the component is mounted
onMounted(async () => {
await this.loadCourses();
});
}
async loadCourses() {
this.state.isLoading = true;
this.state.error = null;
try {
// Making an asynchronous call to our Python controller
// The context mentions passing partner_id; for this example, we'll pass a dummy one
// or you could dynamically get it if the user is logged in, even with 'auth=public'.
const partnerIdFromDOM = document.querySelector('[data-partner-id]')?.dataset.partnerId || 0;
const fetchedCourses = await this.rpc("/my_portal/get_public_courses", {
partner_id: partnerIdFromDOM
});
this.state.courses = fetchedCourses;
this.state.totalCourses = fetchedCourses.length;
// Example of manipulating DOM directly (as discussed in context) - generally avoid this
// but useful for quick updates or integrating with legacy elements.
const totalCoursesElement = document.getElementById('my_public_component_total_courses');
if (totalCoursesElement) {
totalCoursesElement.textContent = `Total Public Courses: ${this.state.totalCourses}`;
}
} catch (error) {
console.error("Failed to load Odoo OWL public components (courses):", error);
this.state.error = "Could not load courses. Please try again later.";
} finally {
this.state.isLoading = false;
}
}
}
// Associate the component with its QWeb template
PublicCourseList.template = "my_public_component_module.PublicCourseListTemplate";
// If your component uses other OWL components, declare them here.
PublicCourseList.components = {};
// Register the component under the 'public_components' category
registry.category("public_components").add("my_public_component_module.PublicCourseList", PublicCourseList);
OWL Component Explained:
importStatements: Essential OWL hooks (Component,useState,onMounted) and theuseServicehook for RPC calls.registryis key for making it a public component.setup()Method: This is where you initialize your component.this.state = useState(...): Defines reactive state variables (courses,isLoading,error,totalCourses). Changes to these will automatically trigger re-renders.this.rpc = useService("rpc"): Provides access to Odoo’s RPC service for making backend calls.onMounted(async () => { ... }): A lifecycle hook that runs once the component is mounted to the DOM. It’s the ideal place to fetch initial data.
async loadCourses()Method:- An
asyncfunction to handle the RPC call. Always useasync/awaitfor asynchronous operations to prevent blocking the UI. await this.rpc(...): Makes the call to our Python controller. We pass an optionalpartner_idas suggested by the context, which could be fetched from a DOM element (data-partner-id).- Updates
this.state.coursesandthis.state.totalCourseswith the fetched data. - Direct DOM Manipulation (Caution): The context mentioned manipulating DOM elements with JavaScript. While OWL prefers a reactive approach,
document.getElementById('my_public_component_total_courses').textContent = ...shows how you could do it. This is generally discouraged for data managed by OWL’s state, but it’s an example for specific needs outside of OWL’s direct reactivity. - Includes
try...catchfor robust error handling.
- An
PublicCourseList.template: Links this JavaScript component to its QWeb XML template. The name must be an exact match.registry.category("public_components").add(...): This line is paramount! It registers yourPublicCourseListcomponent as an accessible public component in Odoo, allowing you to embed it in public QWeb templates.
Step 5: Creating the XML Template (static/src/js/components/course_list.xml)
This QWeb template defines the visual structure of your OWL component.
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="my_public_component_module.PublicCourseListTemplate" owl="1">
<div class="o_public_course_list_component">
<div t-if="state.isLoading" class="alert alert-info">Loading courses...</div>
<div t-if="state.error" class="alert alert-danger"><t t-esc="state.error"/></div>
<h3 id="my_public_component_total_courses">Total Public Courses: <t t-esc="state.totalCourses"/></h3>
<div t-if="!state.isLoading and !state.error">
<t t-if="state.courses and state.courses.length > 0">
<div class="row">
<t t-foreach="state.courses" t-as="course" t-key="course.id">
<div class="col-md-4 mb-3">
<div class="card shadow-sm h-100">
<div class="card-body">
<h5 class="card-title"><t t-esc="course.name"/></h5>
<p class="card-text"><t t-esc="course.description"/></p>
<a t-att-href="course.url" class="btn btn-primary btn-sm mt-2">View Course</a>
</div>
<div class="card-footer text-muted">
Course ID: <t t-esc="course.id"/>
</div>
</div>
</div>
</t>
</div>
</t>
<t t-else="">
<p class="alert alert-warning">No public courses available at the moment.</p>
</t>
</div>
</div>
</t>
</templates>
QWeb Template Explained:
t-name: Must exactly match thePublicCourseList.templateproperty in your JavaScript.owl="1": Declares this as an OWL template.- Conditional Rendering (
t-if,t-else): Displays different content based onstate.isLoading,state.error, orstate.coursesavailability. - Looping (
t-foreach): Iterates overstate.coursesto display each course dynamically. - Data Display (
t-esc): Safely outputs data from your component’s state. - Dynamic Attributes (
t-att-): This is a key insight from the context. To set dynamic HTML attributes, likehrefon an<a>tag, uset-att-attribute_name="expression". For instance,t-att-href="course.url"dynamically sets thehrefattribute to theurlproperty of each course object. This is essential for dynamic elements that need to pull data from your component’s state.
Step 6: Inheriting and Implementing the Component in a Public View (views/portal_templates.xml)
Now, we’ll embed our OWL component into an existing Odoo website page. We’ll inherit the website.homepage template as an example.
<odoo>
<template id="portal_my_public_courses" inherit_id="website.homepage" name="My Public Courses Section">
<xpath expr="//div[@id='wrap']" position="inside">
<section class="s_text_block pt48 pb48" data-snippet="s_text_block" data-name="Text Block">
<div class="container o_container_small">
<div class="row">
<div class="col-lg-12">
<h2 class="text-center mb-4">Explore Our Odoo OWL Public Components Courses!</h2>
<!-- This div can pass dynamic data if needed, or simply act as a target -->
<div id="public_course_list_container" data-partner-id="<t t-esc="request.env.user.partner_id.id if request.env.user.partner_id else 0"/>">
<!-- Your OWL component will be rendered here -->
<t t-component="my_public_component_module.PublicCourseList"/>
</div>
</div>
</div>
<div class="row mt-5">
<div class="col-12 text-center">
<p id="my_public_component_total_courses" class="text-muted">Total Public Courses: 0</p>
</div>
</div>
</div>
</section>
</xpath>
</template>
</odoo>
Template Inheritance Explained:
inherit_id="website.homepage": We’re extending Odoo’s default homepage.xpath expr="//div[@id='wrap']" position="inside": This XPath expression targets a specific element on the homepage (<div id="wrap">) and inserts our new content inside it.<t t-component="my_public_component_module.PublicCourseList"/>: This is how you embed your registered OWL public component! The value oft-componentmust exactly match the name you used when registering the component in theregistryincourse_list.js.data-partner-id: As per the context, we’re using adata-attribute to pass potential dynamic information (like apartner_idif the user is logged in) from QWeb to the OWL component, which can then read it viadocument.querySelector('[data-partner-id]')?.dataset.partnerId.
Step 7: Testing and Troubleshooting
You’ve built your Odoo OWL public components! Now it’s time to see them in action.
- Update Module List: In Odoo, go to Apps -> Update Apps List.
- Install/Upgrade Module: Find
My Public OWL Componentsand install it. If you made changes to an already installed module, upgrade it. - Restart Odoo Server: Always restart your Odoo server after making changes to Python files, manifest, or asset definitions.
- Access Your Website: Navigate to your Odoo website’s homepage. You should see your new course list.
- Browser Developer Tools:
- Console (F12): This is your best friend for debugging. Check for JavaScript errors. The
console.logstatements in yourcourse_list.jswill appear here, confirming if your files are loaded and your RPC calls are successful. - Network Tab: Verify that the
/my_portal/get_public_coursesRPC call is being made and returning the expected data. Look for a successful 200 status code. - Elements Tab: Inspect the HTML structure to ensure your component is rendered as expected.
- Console (F12): This is your best friend for debugging. Check for JavaScript errors. The
- Odoo Server Logs: Check your Odoo server logs for any Python errors from your controller.
Important Troubleshooting Tips (from the Live MasterClass):
- Incremental Development: When starting a new component, begin with a simple “Hello World” in your JavaScript and XML. Confirm it loads before adding complex logic. This helps isolate issues.
- Naming Consistency: The context strongly emphasizes that names must match precisely:
PublicCourseList.templatein JS must matcht-namein XML.- The component name in
registry.category("public_components").add()must match the name used in<t t-component="...">in your QWeb view. Even a capitalization error can cause it to fail silently or with hard-to-diagnose errors.
- Asynchronous (Async/Await): Remember that RPC calls are asynchronous. Always use
asyncon your methods andawaitwhen callingthis.rpcto correctly handle promises and prevent race conditions or unexpected behavior. - Copilot Caution: Be careful with AI code suggestions like Copilot. As highlighted in the context, it sometimes suggests changes (e.g., turning a function into a static property) that are incorrect or introduce subtle bugs. If you’re encountering strange issues, try disabling it temporarily or double-checking its suggestions against official documentation.
t-att-for Dynamic Attributes: Recall that directly assigning dynamic values to attributes like<div partner-id="{{ partner_id }}">is not how QWeb works for dynamic data. You must uset-att-attribute_name="expression"to ensure attributes are evaluated dynamically.- Dependencies: If you’re inheriting from existing Odoo components or modules (like
website_slides), ensure all necessary dependencies are declared in__manifest__.py. Missing dependencies are a common cause of errors.
Conclusion
Congratulations! You’ve successfully built and integrated dynamic Odoo OWL public components into your Odoo website. This powerful approach allows you to create highly interactive and engaging user interfaces, opening up a world of possibilities for custom public-facing features. By diligently following these steps, paying close attention to naming conventions, asynchronous operations, and leveraging browser debugging tools, you are well on your way to becoming an Odoo frontend master. Keep experimenting, keep building, and unleash the full potential of OWL in Odoo!
Discover more from teguhteja.id
Subscribe to get the latest posts sent to your email.

