Enable Dark Mode!
how-to-call-orm-through-the-interaction-class-in-odoo-19.jpg
By: Swaraj R

How to Call ORM Through the Interaction Class in Odoo 19

Technical Odoo 19 Odoo Enterprises Odoo Community

Modern website development in Odoo 19 is no longer about sprinkling JavaScript on the page and hoping it works. Odoo introduces a structured and lifecycle-aware approach through the Interaction class, allowing developers to safely connect frontend behavior with backend data.

One of the most powerful capabilities of the Interaction class is its direct access to Odoo services, including the ORM. This makes it possible to fetch live business data—such as sale orders—directly from JavaScript, while still respecting Odoo’s framework rules, lifecycle, and cleanup mechanisms.

In this article, we will explore:

  • What the Interaction class is
  • Its available lifecycle hooks
  • How selectors control where interactions run
  • And most importantly, how and why ORM calls belong inside willStart(), using a real example

The Interaction class is the foundation for public website behaviors in Odoo. Each interaction:

  • It is automatically instantiated for matching DOM elements
  • Has a well-defined lifecycle
  • Can access Odoo services like orm
  • Can render QWeb templates dynamically
  • Cleans up safely when destroyed (for example, when switching to edit mode)

Unlike Owl components, interactions enhance existing DOM instead of owning it, making them ideal for website pages.

Create a file inside static/src/js/demo_interation.js and link it to web.assets_frontend in the manifest assets.

import { registry } from "@web/core/registry";
import { Interaction } from "@web/public/interaction";
export class DemoInteractionClass extends Interaction {
   static selector = ".show_sale_order";
   setup() {
   }
   async willStart() {
       this.sale_order = await this.services.orm.searchRead(
           "sale.order",
           [],
           ["id", "name", "amount_total", "state"],
           { limit: 10 }
       );
   }
   start() {
       this.renderAt("your_module_name.sale_order_template", {
           orders: this.sale_order,
       }, this.el.querySelector(".main_section"));
   }
}
registry.category("public.interactions").add("your_module_name.unique_interaction_name", DemoInteractionClass);

Create a file inside static/src/xml/sale_order_template.xml and link it to web.assets_frontend in the manifest assets.

<?xml version="1.0" encoding="utf-8"?>
<templates xml:space="preserve">
   <t t-name="your_module_name.sale_order_template">
       <div class="container">
           <t t-if="orders">
               <div class="row">
                   <!-- Loop through orders -->
                   <t t-foreach="orders"
                      t-as="order"
                      t-key="order.id">
                       <div class="col-4 mb-3">
                           <div class="card border rounded h-100">
                               <div class="card-body">
                                   <h5 class="card-title">
                                       <t t-esc="order.name"/>
                                   </h5>
                                   <p class="card-text mb-1">
                                       <strong>ID:</strong>
                                       <t t-esc="order.id"/>
                                   </p>
                                   <p class="card-text mb-1">
                                       <strong>Status:</strong>
                                       <t t-esc="order.state"/>
                                   </p>
                                   <p class="card-text">
                                       <strong>Total:</strong>
                                       <t t-esc="order.amount_total"/>
                                   </p>
                               </div>
                           </div>
                       </div>
                   </t>
               </div>
           </t>
           <t t-else="">
               <p>No sale orders available.</p>
           </t>
       </div>
   </t>
</templates>

Connecting the Interaction Selector to the Website Template

The selector defined in an Interaction class is not abstract—it directly corresponds to an element in the website’s QWeb template. In our case, the interaction is bound to a CSS class rendered on the page, which acts as the entry point for all dynamic behavior.

Selector Defined in the Interaction

Create an XML file inside views/website/website_views.xml

<?xml version="1.0" encoding="utf-8"?><odoo>
   <record id="menu_services" model="website.menu">
       <field name="name">interactions</field>
       <field name="url">/interactions</field>
       <field name="sequence">3</field>
   </record>
   <template id="my_simple_page" name="Simple Website Page">
       <t t-call="website.layout">
           <div class="container mt-5">
               <div class="text-center">
                   <h1>Welcome to Our Custom Page</h1>
                   <p class="lead">
                       View Few of the sale order.
                   </p>
                   <div class="show_sale_order">
                       <div class="main_section"/>
                   </div>
               </div>
           </div>
       </t>
   </template>
</odoo>

Exposing the Interaction Through a Website Controller

Defining an Interaction class and a website template is only part of the story. To actually view the interaction in a browser, the page itself must be served by a website controller. This is where Odoo’s HTTP routing layer comes into play.

In Odoo, website pages are rendered through controllers that map URLs to QWeb templates.

Add the below controller Python file in your module under controller/demo_interaction.py

# -*- coding: utf-8 -*-
from odoo import http
from odoo.http import request
class MyCustomController(http.Controller):
   @http.route('/interactions', type='http', auth='user', website=True)
   def fetch_sale_order(self):
       """Function to fetch the sale order from backend"""
       return request.render('your_module_name.my_simple_page')

static selector = ".show_sale_order";

The selector defines where the interaction should apply.

In the above code:

  • Every element with the class .show_sale_order
  • Automatically gets its own instance of DemoInteractionClass
  • No manual initialization is required

This makes interactions declarative, scalable, and reusable across pages.

The Interaction class provides four main lifecycle methods:

1. setup() {}

  • Runs immediately after the interaction is created
  • DOM (this.el) and services (this.services) are available
  • Intended for synchronous initialization only

In the above code, setup() is intentionally empty, which is perfectly valid when no initialization is needed.

2. willStart() – The Correct Place for ORM Calls

async willStart() {
    this.sale_order = await this.services.orm.searchRead(
        "sale.order",
        [],
        ["id", "name", "amount_total", "state"],
        { limit: 10 }
    );
}

This is the most important lifecycle hook in the above example.

  • It supports asynchronous logic
  • The framework waits for it to complete
  • Data is guaranteed to be available before rendering
  • Prevents race conditions and partial UI states

Here, the interaction:

  • Calls the ORM service
  • Fetches sale order records
  • Stores them on the interaction instance (this.sale_order)

3. start() – Rendering with Confidence

By the time start() runs, the data is already available.

start() {
    this.renderAt(
        "your_module_name.sale_order_template",
        { orders: this.sale_order },
        this.el.querySelector(".main_section")
    );
}

The start() method runs when:

  • The interaction is fully ready
  • Event handlers are attached
  • Async preparation (willStart) is complete

In the above code, start() focuses on presentation only:

  • Rendering a QWeb template
  • Passing ORM-fetched data to the template
  • Injecting it into a specific DOM location

This clean separation keeps logic and UI concerns well organized.

4. destroy() (Not Used, but Still Important)

Even though code does not override destroy(), it is worth mentioning:

  • Interactions can be destroyed (e.g., switching to website edit mode)
  • Any side effects added later should be cleaned here
  • Odoo handles most cleanup automatically if helpers are used

One of the biggest advantages of interactions is native access to Odoo services:

this.services.orm

This means:

  • No custom RPC boilerplate
  • No manual session handling
  • Full consistency with backend security and access rules

The above example uses searchRead, but the same approach applies to:

  • create
  • write
  • unlink
  • Custom model methods

All while staying within the interaction lifecycle.

registry.category("public.interactions").add("your_module.unique_interaction_name", DemoInteractionClass);

This step makes the interaction discoverable by Odoo’s website framework.

Once registered:

  • Odoo scans the DOM
  • Finds matching selectors
  • Instantiates the interaction automatically

This registration is what turns your class into a first-class frontend feature.

Your code demonstrates a recommended pattern for ORM usage in interactions:

  • Selector defines scope
  • willStart() fetches backend data
  • start() handles rendering
  • No async logic in rendering phase
  • Clean and predictable lifecycle usage

This pattern scales well and remains safe even as pages grow more complex.

The Interaction class in Odoo 19 provides a powerful and disciplined way to connect frontend behavior with backend data. By placing ORM calls inside willStart(), developers ensure that data is fetched at the right time, rendering happens without uncertainty, and lifecycle rules are respected.

Your implementation is a clear example of how interactions should be used:

  • Data preparation first
  • UI rendering second
  • Framework-managed lifecycle throughout

When building dynamic website features that rely on real business data, ORM calls from the Interaction class are not just possible—they are the intended approach.

To read more about How to Use the Interaction Class in Odoo 19 Effectively, refer to our blog How to Use the Interaction Class in Odoo 19 Effectively.


Frequently Asked Questions

Why use willStart() for ORM calls?

willStart() ensures data is fetched before rendering. This prevents empty or broken UI.

What does the selector do in an Interaction?

The selector decides where the interaction runs. Any matching element automatically gets the behavior.

What does the start() method do?

start() is used to render the UI. It runs after data is ready from willStart().

Can we use other ORM methods besides searchRead?

Yes, you can use create, write, unlink, and custom methods. They follow Odoo’s security rules automatically.

If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



whatsapp_icon
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, KINFRA Techno Park
Kakkanchery, Calicut
Kerala, India - 673635

location

Kochi

Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.

location

Bangalore

Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message