Why this chapter? Every OWL feature you'll learn later builds on the same three-file skeleton you set up here — get this part solid and the rest of the book is additions, not surprises. What is Odoo trying to solve with this? A predictable, consistent structure for every custom UI piece in the ecosystem, so any Odoo developer can open any addon's
static/srcfolder and immediately know where to look. Real-world application: This is the exact scaffolding you'll reuse on day one of any client project — a custom dashboard widget, a portal form, a kanban card override — before a single line of business logic gets written.
Congratulations on making it through the fundamentals! You now have all the JavaScript knowledge you need to start building with OWL. In this chapter, the theory ends and the practical application begins. We are going to build our very first, classic "Hello, World!" component.
This is the moment where everything you've learned—classes, modules, template literals, destructuring, and the declarative mindset—comes together. By the end of this chapter, you'll have a working OWL component running inside Odoo. Let's get started.
Anatomy of an OWL Component
An OWL component is not a single file. It's a small, self-contained ecosystem typically made of three files, each with a specific responsibility. This separation of concerns is a core principle that keeps your code clean, organized, and maintainable.
Think of it like building a house:
-
The JavaScript File (
.js): This is the brain of your component. It holds the logic, the state, the methods, and defines how the component behaves. This is where all your modern JavaScript skills come into play. -
The XML File (
.xml): This is the skeleton. It defines the structure and layout of your component's UI using QWeb, Odoo's powerful template language. This determines what users see. -
The CSS/SCSS File (
.scss): This is the skin. It contains all the styling rules to make your component look professional and match Odoo's design system. (We'll cover styling in detail in later chapters.)
For our first component, we'll focus on the first two: the JavaScript and the XML file. Don't worry about styling for now—let's get the functionality working first.
Setting Up Your Module Structure
Before we write any code, let's establish a proper file structure. Organization matters, especially as your components grow in complexity.
Create the following directory structure in your Odoo module:
my_odoo_module/
|-- __manifest__.py
|-- static/
| |-- src/
| |-- components/
| |-- hello_world/
| |-- hello_world.js
| |-- hello_world.xml
| |-- hello_world.scss (we'll add this later)
|-- views/
|-- hello_world_views.xml
Why this structure?
- static/src/components/: This is the standard location for OWL components in Odoo modules
- Component-specific folders: Each component gets its own folder (hello_world/) to keep related files together
- Consistent naming: Files are named after the component for easy identification
The JavaScript File: The Component's Logic
Let's create our first component. Create the file my_odoo_module/static/src/components/hello_world/hello_world.js:
/** @odoo-module **/
import { Component } from "@odoo/owl";
import { registry } from "@web/core/registry";
export class HelloWorld extends Component {
static template = "my_odoo_module.HelloWorld";
setup() {
console.log("HelloWorld component is being set up!");
}
}
// Register the component as a client action so Odoo can display it
registry.category("actions").add("hello_world_app", HelloWorld);
Let's break this down line by line:
/** @odoo-module **/: This is a special comment that tells the Odoo asset system to treat this file as a module. It's mandatory for all JavaScript files in Odoo modules. Without it, your component won't be recognized.
import { Component } from "@odoo/owl";: Here we're using the destructuring import syntax we learned about. We're extracting the Component class from the OWL library. This gives us access to all the powerful features of OWL components.
export class HelloWorld extends Component { ... }: This line does several important things:
- export: Makes our class available to other parts of the system (remember modules?)
- class HelloWorld: Defines our component class with a descriptive name
- extends Component: Inherits all the OWL functionality (remember class inheritance?)
static template = "my_odoo_module.HelloWorld";: This is the crucial link between our JavaScript logic and our XML template. The name must be:
- Unique across your entire Odoo instance
- Follow the convention: module_name.ComponentName
- Match exactly what we define in the XML file
setup() { ... }: This is OWL 2.0's modern approach to component initialization. It replaces the old constructor pattern and runs when the component is created. The console.log will help us see when our component is working.
registry.category("actions").add("hello_world_app", HelloWorld);: This registers our component in Odoo's actions registry under the tag hello_world_app. In a moment, we'll create a client action on the server side whose tag field matches this name—that's how Odoo knows which component to render when the action is triggered.
Understanding the Component Lifecycle
Even in this simple example, OWL is doing a lot behind the scenes:
- Component Creation: OWL creates an instance of our
HelloWorldclass - Setup Execution: Our
setup()method runs, allowing us to initialize state and services - Template Rendering: OWL finds the template we specified and renders it
- DOM Insertion: The rendered HTML is inserted into the page
The XML File: The Component's Template
Now let's create the template that our JavaScript file references. Create the file my_odoo_module/static/src/components/hello_world/hello_world.xml:
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="my_odoo_module.HelloWorld" owl="1">
<div class="hello-world-component">
<div class="alert alert-info">
<h1 class="alert-heading">
<i class="fa fa-rocket me-2"></i>
Hello, OWL World!
</h1>
<p class="mb-0">
This is my first OWL component, and it's working perfectly!
</p>
<hr class="my-3"/>
<p class="mb-0">
<strong>Component Name:</strong> HelloWorld<br/>
<strong>Template:</strong> my_odoo_module.HelloWorld<br/>
<strong>Framework:</strong> OWL 2.0
</p>
</div>
</div>
</t>
</templates>
Let's examine the important parts:
<templates xml:space="preserve">: This is the standard wrapper for all Odoo template files. The xml:space="preserve" ensures that whitespace in your templates is handled correctly.
<t t-name="my_odoo_module.HelloWorld" owl="1">: This is where the magic happens:
- <t>: A QWeb template tag that doesn't render itself, just its contents
- t-name="my_odoo_module.HelloWorld": The unique identifier that must exactly match the static template property in our JavaScript class
- owl="1": This crucial attribute tells Odoo's QWeb engine to process this template using the OWL renderer instead of the legacy renderer
The HTML Content: Inside the <t> tag is standard HTML that will become the actual DOM structure of your component. I've used Bootstrap classes (which Odoo includes) to make it look professional right away.
Why This Template Structure?
- Semantic HTML: We use proper HTML5 elements and Bootstrap classes
- Clear visual feedback: The alert styling makes it obvious when the component renders
- Debugging information: We include the component and template names for easy identification
- Professional appearance: Even a "Hello World" component should look good in Odoo
Registering the Component with Odoo
We've created the component files, but Odoo doesn't know about them yet. We need to register them with the asset system.
Step 1: Update the Module Manifest
Open your module's __manifest__.py file and add the new files to the assets dictionary:
# In __manifest__.py
{
'name': 'My OWL Hello World Module',
'version': '1.0',
'depends': ['base', 'web'],
'data': [
'views/hello_world_views.xml',
],
'assets': {
'web.assets_backend': [
'my_odoo_module/static/src/components/hello_world/hello_world.js',
'my_odoo_module/static/src/components/hello_world/hello_world.xml',
],
},
'installable': True,
'auto_install': False,
}
Important points about assets:
- web.assets_backend: This bundle loads on the Odoo backend (where most business applications live)
- Order matters: JavaScript files should generally come before XML files
- Path accuracy: Double-check your file paths—typos here will cause silent failures
Step 2: Create a View to Display the Component
Create a view file at my_odoo_module/views/hello_world_views.xml:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Define a client action to display our component.
The "tag" must match the name used in registry.category("actions").add(...) -->
<record id="action_hello_world" model="ir.actions.client">
<field name="name">Hello OWL World</field>
<field name="tag">hello_world_app</field>
<field name="target">current</field>
</record>
<!-- Add a menu item so users can find your component -->
<menuitem id="menu_hello_world"
name="Hello OWL World"
action="action_hello_world"
parent="base.menu_administration"
sequence="100"/>
</data>
</odoo>
Understanding this structure:
Client Action: Unlike regular Odoo views (form, list, etc.), OWL components often use "client actions"—special actions that render custom JavaScript applications instead of standard views.
The tag field is the link: When the user triggers this action, Odoo looks up hello_world_app in the JavaScript actions registry and finds the HelloWorld component we registered there. No server-side template is needed—the component's own QWeb template does the rendering.
Menu Integration: The <menuitem> gives users a way to access your component through Odoo's standard menu system.
Testing Your Component
Now for the moment of truth! Let's get your component running:
Step 1: Install/Upgrade Your Module
# If this is a new module
./odoo-bin -d your_database -i my_odoo_module --dev=all
# If you're updating an existing module
./odoo-bin -d your_database -u my_odoo_module --dev=all
The --dev=all flag is crucial—it ensures that your JavaScript and XML changes are loaded immediately without needing to restart the server.
Step 2: Navigate to Your Component
- Log into your Odoo instance
- Go to Settings (or wherever you placed your menu item)
- Click on "Hello OWL World"
If everything worked correctly, you should see your beautiful component rendered with the Bootstrap alert styling!
Step 3: Verify in Browser DevTools
Open your browser's DevTools (F12) and:
- Check the Console: You should see the message "HelloWorld component is being set up!"
- Inspect the Elements: You should see the HTML structure from your template
- Check the Network tab: Verify that your
.jsand.xmlfiles were loaded
Common Issues and Solutions
"Component not found" error:
- Check that your static template property exactly matches the t-name in your XML
- Verify that both files are listed in __manifest__.py
- Make sure you've upgraded your module
"Template not found" error:
- Confirm the owl="1" attribute is present in your template
- Check for typos in the template name
- Verify the XML file is valid (no syntax errors)
Component doesn't appear:
- Check the browser console for JavaScript errors
- Verify your client action and menu item are correctly defined
- Make sure you're running with --dev=all
Styling looks wrong: - Odoo includes Bootstrap by default, so our classes should work - Try inspecting the elements to see what CSS is being applied - Remember that we haven't added custom CSS yet—that comes in later chapters
What You've Accomplished
Congratulations! You've just built your first OWL component and integrated it into Odoo. This seemingly simple example demonstrates several crucial concepts:
Component Architecture: You've seen how JavaScript logic and XML templates work together
Modern JavaScript in Practice: You've used import, export, class, and extends in a real application
Odoo Integration: You understand how OWL components fit into Odoo's module system
Development Workflow: You know how to create, register, and test components
Debugging Foundation: You have the basics for troubleshooting when things go wrong
TL;DR: An OWL component is a JS class (static template + setup()) paired with a QWeb XML template (t-name, owl="1") whose names must match exactly; register it with registry.category("actions").add(tag, Component) and an ir.actions.client record sharing that tag.
Try it yourself: This chapter's example is available as an installable Odoo 19 addon: simplifyit_owl_book_ch5_ex1. Installation instructions are in the repository README.
Exercises
- Personalize the greeting. Change the component so it greets a specific name (e.g.,
"Hello, Ana!") by adding a plain JavaScript property insetup()and displaying it witht-escin the template. - Break it on purpose. Rename the
t-namein your XML file so it no longer matchesstatic templatein the JavaScript file, reload the page, and read the exact error Odoo shows you in the console. Then fix it. Recognizing this error message will save you time later. - Add a second action. Add a button that calls a new method logging a message to the console with
console.log, and verify it in the browser DevTools Console tab from Chapter 2.
What's Next?
Right now, this component is static — it shows the same text no matter what. Chapter 6 makes it dynamic: you'll learn the QWeb directives (t-esc, t-if, t-foreach, and more) that let your template react to data coming from the JavaScript side.