Technical

How Registry Works Architecture & Implementation Guide

Published on 06/15/2026
odoo-registry-architecture

What is the Registry?

The Odoo Registry (@web/core/registry) is a central place where frontend modules "announce" what they provide  like components, services, views, or field widgets.

Instead of modules directly depending on each other, they simply register themselves in the Registry. Other parts of Odoo can then find and use them when needed.

How the Registry Works

01

Register Features

Services, views, components, field widgets, and actions register themselves in the Registry using unique keys.

02

Runtime Lookup

Whenever Odoo needs a feature, it queries the Registry to retrieve the registered implementation.

03
Load Implementation

The correct implementation is returned without modules knowing where it was defined.

Why It Matters

Modular

Features remain independent, making the frontend easier to organize and maintain.

Flexible

Existing implementations can be replaced or extended without changing dependent modules.

Extensible

New modules can plug into the system without modifying existing frontend code.

Key Concept

The Registry Stores References, Not Business Logic

The Registry acts as a central lookup table. It never executes business logic, validates data, or performs application tasks. Its sole responsibility is to maintain references between keys and their registered implementations, allowing modules to remain independent while enabling dynamic extension and replacement.

The Registry always answers:
“Which implementation is currently registered for this key?”

Three Core Properties

Registry Benefit

2.1 Flexible Coordination

The Odoo Registry removes hard JavaScript dependencies between modules. Instead of importing components directly, applications simply request the required implementation from the registry, enabling a clean, loosely coupled plugin architecture.

Live Updates

2.2 Reactive Store

Every registry extends EventBus. Whenever a new item is registered, an UPDATE event is emitted, allowing subscribed Owl components to react automatically without requiring a page reload.

Safe Registration

2.3 Validated Contracts

Categories using addValidation() verify every registry entry against the expected structure. Invalid registrations fail immediately, helping developers detect integration issues before they reach the user interface.

Important

addValidation() is not available for every registry category. Only categories that define a validation schema enforce structural checks and throw an error when invalid data is registered. Categories without validation accept any object, making it the developer's responsibility to register entries that follow the expected contract.

3. Runtime Lifecycle

The Registry transitions through four phases from static definitions to a live runtime environment:

Phase Name What Happens Registry Role
I Static Registration ES modules execute their top-level code as the asset bundle loads Modules call registry.category(...).add(...) to publish items
II Contract Enforcement Categories with addValidation() verify the shape of registered items Malformed registrations throw immediately at this point
III Service Graph Resolution startServices(env) resolves the dependency graph and boots services Service registry is read; dependency order is computed
IV Dynamic Discovery OWL components render and look up implementations as needed Components call registry.get() to find widgets, views, actions at runtime
Lifecycle Note

Static registration (Phase I) completes before any OWL component is mounted. As a result, all statically imported registry entries are already available when the UI starts rendering. Additional entries from plugins or lazy-loaded modules can be registered later, automatically notifying subscribed components through the EventBus UPDATE event and triggering the required UI updates.

4. Registry Categories

Registries are organized into named categories. Each category is an independent store for a specific type of item. The global registry object is the entry point: registry.category(name) returns the category store for that name, creating it if it does not yet exist.

Category Name Registered Items Primary Consumer
fields Field widget components (CharField, IntegerField, etc.) Form view and List view renderers
views View implementations (Form, List, Kanban, etc.) ActionManager and WebClient
services Service factories (Action, Notification, RPC, etc.) startServices() and useService()
actions Client action handlers ActionManager
systray Top navigation bar icon components NavBar and WebClient
main_components Root-level mounted Owl components WebClient mounting loop

5. Item Resolution Model

Resolution Strategy

Direct Key Lookup

The simplest and fastest lookup mechanism is a direct key lookup. Calling registry.category(...).get(...) retrieves the component registered under the specified key. Since registry entries are internally stored as key-value mappings, this lookup executes in constant time (O(1)), making it extremely efficient even when a category contains many registered items.

O(1) Performance

Registry lookups use direct key access, allowing extremely fast retrieval regardless of registry size.

Exact Match

The Registry returns the component mapped to the requested key. If no matching entry exists, an error is raised.

Used Everywhere

This lookup pattern powers field widgets, services, actions, views, menus, and most Registry-based extensions.

JavaScript Example Direct Registry Lookup
// Retrieve the widget registered for Char fields

const FieldComponent = registry
    .category("fields")
    .get("char");

// Returns the registered CharField component.
// Throws an error if no matching key exists.
Resolution Strategy

Scoped / Priority Lookup

While direct key lookups are sufficient in many cases, field widgets require a more intelligent resolution strategy. Odoo's getFieldFromRegistry() searches multiple registry keys in a predefined priority order, always selecting the most specific widget before falling back to more generic implementations.

Highest Priority

Odoo first searches for a widget registered specifically for the current view and widget combination, ensuring the most context-aware implementation is selected.

Automatic Fallback

If no view-specific widget exists, the Registry automatically falls back to a generic widget before finally using the default field type implementation.

Flexible Customization

Developers can override widgets for a single view without affecting every other view, making customizations cleaner and easier to maintain.

Priority Registry Key Resolution Behaviour
1 viewType.widgetName Searches for a widget registered specifically for the current view type and widget combination.
2 widgetName Falls back to a generic widget that works across every view.
3 fieldType Uses the default widget associated with the field type such as char, integer, or boolean.
JavaScript Example Priority Lookup
// Odoo searches registry keys in this order

form.my_widget
        ↓
my_widget
        ↓
char
Key Takeaway

This layered lookup strategy enables highly targeted customizations. You can override a widget for a specific view without replacing the global implementation, allowing custom modules to coexist while preserving Odoo's default behavior.

Ordering Strategy

Sequence-Based Ordering

Some registry categories contain multiple items that must appear in a predictable order. Instead of relying on registration order, Odoo uses a sequence value to determine the final ordering. Items with lower sequence numbers are processed before items with higher values.

Ordered Execution

Registry entries are returned in ascending sequence order, ensuring deterministic rendering and execution.

UI Positioning

Categories such as systray use sequence values to determine the visual order of components.

Easy Customization

Developers can reposition components simply by changing their sequence value without modifying existing code.

Sequence Position Result
10 First Rendered before all default items.
50 Default Standard ordering used by most registry entries.
100 Last Appears after entries with lower sequence values.
JavaScript Example Sequence Registration
registry.category("systray").add(
    "my_icon",
    MyIcon,
    { sequence: 10 }
);

// Lower sequence values appear first.
Best Practice

Avoid using the same sequence value for multiple custom components whenever possible. Meaningful spacing (10, 20, 30...) makes future insertions easier without renumbering existing entries.

Conflict Management

Conflict Resolution

Every registry key must be unique. If a module attempts to register an item using a key that already exists, Odoo immediately raises a DuplicatedKeyError. This prevents accidental overrides and keeps registry contents predictable. When replacing an existing entry is intentional, developers can pass the { force: true } option.

Unique Keys

Every registry entry must use a unique key. Duplicate registrations are rejected immediately to prevent conflicts.

Force Replacement

Passing { force: true } intentionally replaces an existing registry entry instead of throwing an exception.

Extension First

Whenever possible, extend existing functionality using patch() instead of replacing the entire registry entry.

Action Result Recommended?
Normal Registration Creates a new registry entry. ✅ Yes
Duplicate Key Throws DuplicatedKeyError. ❌ No
{ force: true } Replaces the existing registry item. ⚠ Only when required
patch() Extends existing behavior without replacing it. ⭐ Best Practice
JavaScript Example Force Replacement
registry.category("fields").add(
    "char",
    MyCharField,
    { force: true }
);

// Replaces the existing "char" widget.
Best Practice

Avoid overusing { force: true }. When multiple modules replace the same registry key, only the last registration survives. If your goal is to extend existing behavior, prefer patch() because it preserves compatibility with other custom modules and future Odoo upgrades.

6. Service Orchestration

Services registered in the "services" category are not simple objects  they are factory functions that the Service Launcher resolves, orders, and instantiates during startup.

6.1

Lazy Initialization

Services start only when their declared dependencies are ready. The launcher does not simply iterate the registry in registration order—it builds a dependency graph and resolves it. A service that depends on rpc will not start until rpc has finished its start() call.

6.2

Parallel Boot

Services with no mutual dependencies can boot concurrently. The launcher only enforces dependency relationships, allowing independent services to initialize in parallel and reduce application startup time.

6.3

Async Readiness

Service start() functions can return a Promise. The launcher waits for that promise to resolve before starting dependent services, ensuring asynchronous initialization completes before the service becomes available.

6.4

Circular Dependency Detection

If two services declare each other as dependencies, the Service Launcher detects the cycle during startup and throws an error. Circular dependencies are intentionally blocked and must be resolved by redesigning the dependency graph.

Mechanism Runtime Behavior Developer Consideration
Lazy Initialization Services start only after every declared dependency is fully initialized. Declare all required services in the dependencies array.
Parallel Boot Independent services initialize simultaneously whenever possible. Never rely on the startup order of unrelated services.
Async Readiness The launcher waits for the start() Promise before starting dependent services. Return a Promise whenever initialization performs asynchronous work.
Circular Dependency Check Cyclic dependencies are detected during startup and immediately raise an error. Break dependency cycles by redesigning service relationships.

7. OWL Integration

7.1

useRegistry Hook

Components that need to react to registry changes use the useRegistry() hook. It subscribes the component to the UPDATE event of the specified registry category. Whenever new items are registered, the component automatically re-renders to reflect the latest Registry state.

import { useRegistry } from "@web/core/registry";

setup() {
    super.setup();

    const fieldRegistry = useRegistry(
        registry.category("fields")
    );
}

Event throttling is built in. Multiple registrations occurring within the same JavaScript tick trigger only a single re-render, preventing unnecessary rendering work during bulk registrations.

7.2

useService Hook

The useService() hook provides access to running service singletons through the OWL environment. It returns a reactive proxy, allowing components to automatically update when the service's reactive state changes.

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

setup() {
    super.setup();

    this.notification = useService("notification");
    this.actionService = useService("action");
}

IMPORTANT

useService() can only be called during setup(). It accesses env.services, which becomes available only after startServices() completes. Calling the hook before services are initialized—or outside setup()—throws an error.

8. Registry vs patch() — When to Use Which

The most common architectural decision in Odoo module development is choosing between extending via the Registry or via patch(). They solve different problems.

Area Registry patch() Recommended Usage
Primary Goal Register or replace a named item within a registry category. Extend or modify the behavior of an existing class or object. Use Registry for new implementations and patch() for behavior changes.
Modification Scope Replaces the entire registered item. Overrides individual methods without replacing the whole object. Choose patch() for targeted modifications; use Registry only for full replacements.
Original Logic Not applicable. Supports super to invoke the original implementation. If the original behavior must be preserved, prefer patch().
Debugging Easy to inspect through Registry categories and keys. Requires inspecting the prototype chain and patched methods. Registry-based extensions are generally easier to trace and maintain.
Maintenance Risk Low — explicit, key-based registration. Moderate — depends on prototype structure and implementation details. Prefer Registry whenever an extension point already exists.

Preferred Extensibility Order

Always follow this order when deciding how to extend Odoo:

1

Registry

Add or replace a named item (view, field widget, service, action).

2

Hooks (useX)

Encapsulate reusable stateful logic.

3

Services

Provide cross-component logic through env.services.

4

patch()

Last resort for modifying existing behavior when no Registry extension point exists.

9. Anti-patterns & Constraints

9.1

Don't Over-use { force: true }

Using { force: true } is a silent win-by-last-write. If two modules both force the same key, the second silently erases the first. There is no warning, no merge, and no super chain. For reusable modules, prefer patch() whenever possible.

9.2

No Circular Service Dependencies

The Service Launcher builds a directed dependency graph. Circular dependencies are detected during startup and immediately throw an error. Resolve them by refactoring services or introducing an intermediate communication layer.

9.3

Don't Register in setup()

Registry entries should be registered exactly once during module loading. Registering items inside setup() creates duplicate registrations whenever a component is mounted.

9.4

Validate Your Schema

When creating a Registry category that accepts third-party contributions, define an addValidation() schema. Invalid registrations are then rejected immediately instead of causing runtime failures.

10. Version Evolution

Capability Odoo 16 / 17 Odoo 18 / 19
Registry Architecture Basic object store with add() and get(). EventBus-powered reactive Registry with automatic update notifications.
Validation Limited or no built-in schema validation. Strong registration validation through addValidation().
Reactivity Manual subscriptions and update handling. Reactive updates via useRegistry() and useService().
Service Startup Sequential startup with basic dependency checks. Dependency graph resolution, parallel boot, async readiness, and circular dependency detection.
Module System Legacy odoo.define() modules. Native ES Modules using @odoo-module.

11. Debugging the Registry

11.1

Global Inspection

In a browser console on any Odoo page, the running service instances are accessible at:

odoo.__WOWL_DEBUG__.root.env.services
// Returns an object with all running service instances keyed by name

To inspect a specific Registry category:

odoo.__WOWL_DEBUG__.root.env.services.registry.category("fields").getAll()

// Returns all registered field widget entries
// including their sequence and value.
11.2

Service Startup Tracepoint

To debug when and in what order a service starts, place a debugger; statement inside its start() function. The browser call stack reveals the Service Launcher's dependency resolution path, making it easy to identify which dependency triggered the service startup.

const myService = {
    dependencies: ["rpc", "notification"],

    start(env, { rpc, notification }) {
        debugger; // Inspect the call stack here

        return {
            // Service API
        };
    }
};

Service Startup Tracepoint

To understand when and why a service starts, place a debugger; statement inside its start() method. When execution pauses, inspect the browser call stack to identify which dependency triggered the service and the order in which the Service Launcher resolved it.

JavaScript Example Service Debugging
const myService = {
    dependencies: ["rpc", "notification"],

    start(env, { rpc, notification }) {
        debugger;   // Inspect startup order here

        return {
            /* Service API */
        };
    },
};

Diagnosing DuplicatedKeyError

Symptom Likely Cause Recommended Fix
DuplicatedKeyError during module load Two modules register the same key within one registry category. Rename the key or intentionally replace it using { force: true }.
{ force: true } entry disappears Another module force-registers the same key later. Search all .add() calls and verify asset loading order.
useService() reports "not found" Service failed to register or never completed startup. Verify service name and inspect dependency startup failures.
UPDATE event without UI refresh Component isn't subscribed through useRegistry(). Replace manual listeners with the reactive useRegistry() hook.

Asset Debug Mode

Append ?debug=assets to the Odoo URL to load individual, unminified JavaScript files instead of bundled assets. This makes it easy to inspect source files, trace Registry registrations, locate .add() calls, and understand the exact asset loading order.

Browser URL Debug Assets
http://localhost:8069/web?debug=assets

Odoo Registry at a Glance

Topic Key Takeaway
Import Path @web/core/registry is the primary entry point for accessing the Odoo Registry.
Architecture Pattern Implements a centralized Service Locator, eliminating direct module dependencies.
EventBus Every Registry category extends EventBus and emits an UPDATE event whenever a new item is registered.
useRegistry() Subscribes OWL components to Registry updates, triggering automatic re-rendering when registrations change.
useService() Retrieves running service singletons from env.services. It must be called inside setup().
Sequence Items are returned in ascending sequence order (default: 50) during iteration.
Conflict Handling Duplicate keys throw DuplicatedKeyError. Use { force: true } only for intentional replacements.
Validation Categories using addValidation() verify item structure during registration.
Service Boot Services start through dependency graph resolution with lazy initialization, parallel execution, and async support.
Circular Dependencies Circular service dependencies are detected during startup and blocked before execution.
Registry vs patch() Registry registers or replaces complete components, while patch() extends existing class behavior.
Preferred Extension Order Registry → Hooks → Services → patch()
Debugging Inspect services with odoo.__WOWL_DEBUG__.root.env.services and use ?debug=assets to load unminified JavaScript.
Global Reach

Consult Expertise

Engage with our core laboratory engineering leads to optimize your infrastructural frameworks.