FleetbaseFleetbase

Extension Registration

How addon/extension.js wires your extension into the Fleetbase console boot lifecycle. The setupExtension hook, onEngineLoaded, and the contract every extension implements.

Extension Registration

Every Fleetbase extension declares a single addon/extension.js at the root of its addon. The console's ExtensionManager finds this file at boot, dynamic-imports it, and runs its registration hooks.

This is the single canonical entry point — registration code does not live in engine.js, instance initializers, or anywhere else.

The addon/extension.js Contract

addon/extension.js exports a default value that is one of:

Form 1: A plain object with hooks

// addon/extension.js
export default {
    setupExtension(app, universe) {
        // run before any engine loads — register menu items, widgets, hooks, registries
    },

    onEngineLoaded(engineInstance, universe, app) {
        // run when *this* extension's engine boots (lazy, on first navigation)
    },
};

Form 2: A single function

// addon/extension.js
export default function (app, universe) {
    // equivalent to setupExtension only
}

The object form is what every modern extension uses because it lets you mix setupExtension and onEngineLoaded in one place.

setupExtension(app, universe)

Called once at console boot, before any engine has loaded. This is where you do the bulk of registration:

ArgumentTypeDescription
appApplicationInstanceThe host Ember application — useful for app.lookup('service:router') etc.
universeUniverseServiceThe central facade — see Universe Service

Use it to:

  • Register top-level menu entries via universe.getService('menu')
  • Create custom registries via universe.getService('registry')
  • Register dashboard widgets via universe.getService('widget')
  • Hook into lifecycle events via universe.getService('hook')
  • Schedule cross-engine work via universe.whenEngineLoaded(...)
import { MenuItem, Widget, ExtensionComponent, Hook } from '@fleetbase/ember-core/contracts';

export default {
    setupExtension(app, universe) {
        const menuService     = universe.getService('menu');
        const widgetService   = universe.getService('widget');
        const registryService = universe.getService('registry');
        const hookService     = universe.getService('hook');

        menuService.registerHeaderMenuItem('My Extension', 'console.my-extension', {
            icon: 'puzzle-piece',
            description: 'My custom logistics feature.',
        });

        widgetService.registerWidgets('dashboard', [
            new Widget({
                id: 'my-stats',
                name: 'My Stats',
                icon: 'chart-line',
                component: new ExtensionComponent('@my-org/my-extension-engine', 'widget/my-stats'),
                grid_options: { w: 6, h: 6, minW: 4, minH: 4 },
            }),
        ]);

        registryService.createRegistries(['my-extension:sidebar']);

        hookService.registerHook(
            new Hook('console:after-model', (session, router) => {
                // …
            })
        );
    },
};

onEngineLoaded(engineInstance, universe, app)

Called after your extension's Ember Engine has booted. The engine is loaded lazily — typically on first navigation to a route inside it, or on demand via universe.ensureEngineLoaded(...).

ArgumentTypeDescription
engineInstanceEngineInstanceThe booted engine — engineInstance.lookup('service:my-service') works here
universeUniverseServiceThe same facade as above
appApplicationInstanceThe host application

Use it for engine-internal setup that needs the engine container — registering services into the engine itself, reading config from the engine, etc. Most extensions don't need it; setupExtension covers the common cases.

When to Use What

NeedUse
Register a header menu itemsetupExtension
Add a widget to the default dashboardsetupExtension
Register a hook (application:before-model etc.)setupExtension
Create a registry for other extensions to plug intosetupExtension
Inject a component into another extension's slotsetupExtension (via whenEngineLoaded if the host engine isn't booted yet)
Set up something that needs your engine's containeronEngineLoaded
Read your engine's configonEngineLoaded

Cross-Extension Coordination

When your extension needs to register something into another engine that may not be booted yet, wrap the work in universe.whenEngineLoaded:

export default {
    setupExtension(app, universe) {
        // VROOM registers a route-optimization provider into Fleet-Ops
        universe.whenEngineLoaded('@fleetbase/fleetops-engine', async (fleetopsEngine, universe) => {
            const myEngine = await universe.extensionManager.ensureEngineLoaded('@fleetbase/vroom-engine');

            const routeOptimization = fleetopsEngine.lookup('service:route-optimization');
            const vroom = myEngine.lookup('service:vroom');

            routeOptimization.register('vroom', vroom);
        });
    },
};

whenEngineLoaded runs the callback immediately if the engine is already booted, or queues it for when the engine boots. This is the recommended cross-engine pattern.

onEngineLoaded(engineName, cb) is also available but only fires for new loads — it doesn't run for engines that booted before you registered the listener.

The Boot Lifecycle

1. Console application boots

2. ExtensionManager.loadExtensions()
       └── fetches the list of enabled extensions from the API

3. ExtensionManager.setupExtensions()
       ├── for each extension:
       │     ├── dynamic-import addon/extension.js
       │     ├── call setupExtension(app, universe)   ← your registrations run here
       │     └── store onEngineLoaded for later


4. universe.executeBootCallbacks()

5. User navigates to a route inside an extension engine

6. Ember loads the engine lazily
       ├── universe.extensionManager fires `engine.loaded`
       └── stored onEngineLoaded callbacks for that engine fire

Everything you register in setupExtension is in place before any engine boots — so menu items, widgets, and hooks are all visible from the moment the console loads, even if the user never navigates into your extension.

Real-World References

ExtensionPattern shownSource
StorefrontHeader menu + dashboard widget + registry componentsstorefront/addon/extension.js
VROOMwhenEngineLoaded to register an optimization provider into Fleet-Opsvroom/addon/extension.js
ValhallawhenEngineLoaded to register a routing engine into Fleet-Opsvalhalla/addon/extension.js
LedgerHeader menu + dashboard, widgets, and a Fleet-Ops order-details tabledger/addon/extension.js

Source

FileDescription
addon/services/universe/extension-manager.jsImplements setupExtensions(), the boot loop
addon/services/universe.jsThe UniverseService facade
Extension Registration | Fleetbase