Advertisement
🦉 Advanced OWL

OWL JS Environment & Services – useEnv and useService

When data or functionality needs to be available to many components at different depths of the component tree, prop drilling becomes unmanageable. OWL solves this with the environment — an object shared by all components in an application that any component can access via useEnv(). In Odoo, the environment hosts the services system: a registry of cross-cutting capabilities (ORM, notifications, navigation, user info) injected into any component with useService(). This lesson covers both the raw OWL environment and the Odoo services layer.

⏱️ 20 min read 🎯 Intermediate 📅 Updated 2026 👁️ Lesson 4 of 6

useEnv() — The Shared Environment

Every OWL component has access to a shared env object. useEnv() returns it. In a standalone app you populate it when calling mount(); in Odoo it is already populated by the framework.

JavaScript – standalone app: providing env
import { mount } from "@odoo/owl";
import App from "./App";

// Pass env via mount options — available to ALL descendants
mount(App, document.getElementById("app"), {
  env: {
    // Any data or functions you want globally available
    config: {
      apiBase:  "https://api.example.com",
      theme:    "light",
      locale:   "en-US",
    },
    formatDate(date) {
      return new Intl.DateTimeFormat("en-US").format(date);
    },
  },
});
JavaScript – reading env in any component
import { Component, xml, useEnv } from "@odoo/owl";

class InvoiceDate extends Component {
  static template = xml`
    <span t-esc="formatted"/>
  `;

  setup() {
    // Access the shared environment — works at ANY depth in the tree
    this.env = useEnv();
  }

  get formatted() {
    // Use the shared formatDate function from env
    return this.env.formatDate(this.props.date);
  }
}

useService() in Odoo

Odoo extends the OWL environment with a services registry. useService("serviceName") is a custom hook that reads the named service from env.services. Services are singletons — one instance per application.

JavaScript – useService usage
/** @odoo-module **/
import { Component, xml, useState, onWillStart } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";

class SaleOrderList extends Component {
  static template = xml`
    <div>
      <t t-if="state.isLoading"><span>Loading…</span></t>
      <table t-else="">
        <tr t-foreach="state.orders" t-as="order" t-key="order.id">
          <td t-esc="order.name"/>
          <td t-esc="order.partner_id[1]"/>
          <td t-esc="order.amount_total"/>
          <td>
            <button t-on-click="() => openOrder(order.id)">Open</button>
          </td>
        </tr>
      </table>
    </div>
  `;

  state = useState({ orders: [], isLoading: true });

  setup() {
    // Inject services — call in setup() only
    this.orm          = useService("orm");
    this.action       = useService("action");
    this.notification = useService("notification");
    this.user         = useService("user");

    onWillStart(() => this.loadOrders());
  }

  async loadOrders() {
    try {
      this.state.orders = await this.orm.searchRead(
        "sale.order",
        [["user_id", "=", this.user.userId]],
        ["name", "partner_id", "amount_total", "state"],
        { limit: 50, order: "date_order desc" }
      );
    } catch {
      this.notification.add("Failed to load orders", { type: "danger" });
    } finally {
      this.state.isLoading = false;
    }
  }

  openOrder(id) {
    this.action.doAction({
      type:     "ir.actions.act_window",
      res_model: "sale.order",
      res_id:   id,
      views:    [[false, "form"]],
    });
  }
}
Advertisement

Odoo Services Reference

Service nameImportKey methods / properties
"orm"useService("orm")read(model, ids, fields), searchRead(model, domain, fields), write(model, ids, vals), create(model, vals), unlink(model, ids), call(model, method, args)
"notification"useService("notification")add(message, { type, sticky, buttons }) — type: "info", "success", "warning", "danger"
"action"useService("action")doAction(action), doAction(xmlid), switchView(type)
"user"useService("user")userId, name, lang, tz, isAdmin, hasGroup(xmlid)
"router"useService("router")navigate(href), current.hash, pushState(state)
"dialog"useService("dialog")add(DialogComponent, props) — open dialogs programmatically
"company"useService("company")currentCompany, allowedCompanies, currency info
"rpc"useService("rpc")Low-level RPC calls (prefer orm service when possible)

env vs Props — When to Use Each

Use props whenUse env/services when
Data is specific to one component or its subtreeData/capability needed by many components at any depth
Parent explicitly controls what child receivesGlobal cross-cutting concerns (auth, theming, i18n, ORM)
The relationship is direct parent → child3+ levels of depth (prop drilling becomes unmanageable)
You want type-safe, documented component interfaceSingletons that must not be duplicated

The env: Shared Context for the Whole Component Tree

The env is a single object shared by every component in the tree. It's how app-wide things — services, configuration, translation functions — reach any component without being threaded through props at every level ("prop drilling").

import { useEnv, useService } from "@web/core/utils/hooks";

class Widget extends Component {
    setup() {
        this.env;                          // the shared environment object
        this.orm = useService("orm");      // services live on the env
        // this.env.bus, this.env._t (translation), etc.
    }
}

Services (ORM, notification, dialog) are registered onto the env at startup, which is exactly why useService("orm") works in any component — it's reading from the shared env under the hood.

AccessUse for
useEnv()read the env directly
useService(name)grab a service (the common case)
sub-environmentoverride env for a subtree

Why it exists: passing a notification service down through ten layers of props would be miserable and fragile. The env gives descendants a shared channel for truly global concerns. Sub-environments: a component can extend the env for its subtree (e.g. provide a scoped config or a form context) — children see the additions, the rest of the app doesn't. Guideline: use the env for cross-cutting infrastructure (services, i18n, buses); use ordinary props for the specific data a component needs. Overstuffing the env turns it into a hidden global.

🏋️ Practical Exercise

Build a CurrentUserBadge Odoo component using services:

  1. Use useService("user") to get the current user's name, language, and timezone.
  2. Display the name with a greeting based on time of day (Morning/Afternoon/Evening) — compute it from new Date().getHours().
  3. Add a "Logout" button that calls window.location.href = "/web/session/logout".
  4. Add an "Admin Panel" button visible only when user.isAdmin is true. Clicking it calls action.doAction("base_setup.action_general_configuration").
  5. Show a notification using useService("notification") when the component mounts: "Welcome back, [name]!".

🔥 Challenge Exercise

Use the OWL environment (env) to share global services/config down the component tree without prop-drilling — e.g. a translation function or a service. Explain what the env is, how it is provided at mount, and when to use it vs props.

📋 Summary

  • useEnv() returns the shared environment object available to all components — the OWL equivalent of React Context.
  • In standalone apps, provide the env via the env option of mount().
  • In Odoo, use useService("name") (from @web/core/utils/hooks) to inject framework services.
  • Key Odoo services: orm (database), notification (toasts), action (navigation), user (current user), dialog (programmatic dialogs).
  • Services are singletons — one instance per app — injected wherever needed without prop drilling.
  • Always call useService() in setup() — not in methods or async callbacks.

Interview Questions

  • What is the OWL environment (env)?
  • How do you access the env?
  • How is the env provided?
  • When would you use env instead of props?
  • What typically lives in the env?

FAQ

Can I modify the env after mount? +

The env object itself is frozen in OWL (you cannot add or delete top-level keys after mount). However, if your env contains reactive objects (created with reactive() from OWL), mutations on those objects will cause components that read them to re-render. For Odoo, env mutation is managed by the framework — you do not modify it directly.

Can I use useService outside of Odoo? +

useService is an Odoo-specific utility in @web/core/utils/hooks. It reads from env.services which Odoo populates. In a standalone OWL app (without Odoo), use useEnv() directly and read whatever you put in the env. You can implement your own service-like pattern by putting service objects into the env at mount() time.