Understanding Controllers in Odoo's JavaScript Backend

May 29, 2026 by
Understanding Controllers in Odoo's JavaScript Backend
Fazri Muhammad Yazid
| No comments yet

Overview

When diving into Odoo development, much of the focus is often on the Python backend. However, creating a truly dynamic, interactive user experience requires a solid understanding of Odoo's web client specifically its JavaScript framework. A crucial part of this architecture is the Controller. In Odoo's frontend MVC (Model-View-Controller) design pattern, the Controller acts as the brain of a view. It serves as the intermediary between the data (Model) and the user interface (Renderer/View). This blog will explore what a Controller is in the context of Odoo's JS backend, its core functions, and how you can implement and extend it in your own projects.


What is a Controller in the Odoo JS Backend?

In Odoo's web client, views like List, Form, Kanban, and Pivot do not just render HTML; they handle complex interactions, data fetching, and state management. To manage this complexity, Odoo splits the responsibility into three parts:

  1. Model: Manages the state and communicates with the Python backend via RPC calls.
  2. Renderer: Responsible for rendering the user interface and capturing user inputs.
  3. Controller: The coordinator. It listens to user actions from the Renderer, tells the Model to update data, and then instructs the Renderer to refresh the UI.

In the modern Odoo framework (using OWL - Odoo Web Library), Controllers are JavaScript components (like ListController or FormController) that encapsulate the business logic of the user interface.


What Is The Primary Functions?

The Controller wears many hats in the Odoo web client. Its main responsibilities include:

  • Event Handling: It listens to events triggered by the user in the Renderer (e.g., clicking "Save", discarding changes, or clicking a custom action button).
  • Action Routing: It determines what should happen when an event occurs. For instance, if a user clicks a "Print" button, the controller catches that action and executes the necessary logic to generate the report.
  • Coordinating Data: It requests data from the Model and passes it down to the Renderer. When a user input changes the data, the Controller ensures the Model validates and saves that data to the database.
  • Managing View State: The Controller keeps track of the current state of the interface such as whether a form is in "readonly" or "edit" mode.


How to Use a Controller in Your Project

In most real-world scenarios, you won't build a Controller from scratch. Instead, you will extend or patch an existing Controller (like ListController or FormController) to add custom behaviors, such as a new button in the control panel.

Here is a practical example of how to extend the standard ListController using Odoo's OWL framework to add a custom button click handler.


Step 1: Create your JavaScript File

Create a new JS file in your custom module, e.g., static/src/js/custom_list_controller.js.


Step 2: Patch the Controller

We will use Odoo's patch utility to inject our custom logic into the existing ListController.

/** @odoo-module **/

import { ListController } from "@web/views/list/list_controller";
import { patch } from "@web/core/utils/patch";
import { useService } from "@web/core/utils/hooks";

// Patching the ListController to add custom behavior
patch(ListController.prototype, {
    setup() {
        // Always call super.setup() first to preserve original behavior
        super.setup(...arguments);
       
        // Injecting the action service to trigger backend actions
        this.actionService = useService("action");
    },

    /**
     * Custom function to be triggered by a button in the UI
     */
    async onClickMyCustomButton() {
        // Example: Get the currently selected records in the list view
        const selectedRecordIds = await this.model.root.selection.map(
            (record) => record.resId
        );

        if (selectedRecordIds.length === 0) {
            alert("Please select at least one record!");
            return;
        }

        // Example: Triggering a Python action
        await this.actionService.doAction({
            type: "ir.actions.server",
            res_model: this.props.resModel,
            context: {
                active_ids: selectedRecordIds,
            },
            // Replace with your actual Server Action ID
            action_id: 123,
        });
    }
});


Step 3: Link the Controller to a Custom Button

To make the button visible, you would typically extend the View's XML template (usually the Control Panel or the Header) and bind the button's t-on-click to the function you just created in the controller:

<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
    <!-- Extending the List View Control Panel -->
    <t t-inherit="web.ListView.Buttons" t-inherit-mode="extension">
        <xpath expr="//div[hasclass('o_list_buttons')]" position="inside">
            <button type="button"
                    class="btn btn-primary"
                    t-on-click="onClickMyCustomButton">
                Run Custom Action
            </button>
        </xpath>
    </t>
</templates>


Understanding Controllers in Odoo's JavaScript Backend
Fazri Muhammad Yazid May 29, 2026
Share this post
Archive
Sign in to leave a comment