FleetbaseFleetbase

Overview

How extensions contribute UI into each other — registries, slots, and the <RegistryYield> component. Driven by addon/extension.js.

Registry & Slots

Fleetbase extensions are isolated by default — each one ships its own components, services, and routes. But many features are richer when extensions can plug UI into each other:

  • A payment-gateway extension contributing a configuration form into Ledger's gateway settings
  • An analytics extension contributing a chart into the FleetOps dashboard
  • A document-signing extension contributing actions into Storefront orders

The registry system is how this happens. Extensions register components or menu items into named registries; the host extension uses <RegistryYield> to iterate and render them.

How It Works

Extension A (e.g. flespi)                       Host extension (e.g. fleet-ops)

   addon/extension.js
   └── setupExtension(app, universe) {           <RegistryYield
         universe                                  @registry="fleet-ops:component:vehicle:details"
           .getService('menu')                     as |Component context|>
           .registerMenuItem(                        <Component @vehicle={{context}} />
             'fleet-ops:component:vehicle:details', </RegistryYield>
             new MenuItem({ ... }))
       }

Both halves are decoupled — the host doesn't need to know which extensions will contribute, and contributors don't need to know what's downstream.

Registries

A registry is a named bag of items. The naming convention is <host-extension>:<slot-name>:

Example registryWhat it holds
engine:fleet-opsSettings menu items inside Fleet-Ops
fleet-ops:contextmenu:vehicleRight-click context-menu items on a vehicle
fleet-ops:component:vehicle:detailsTabs / panels on the vehicle detail view
fleet-ops:component:map:drawerItems in the Fleet-Ops map drawer
fleet-ops:template:settings:routingComponents in the routing settings
auth:loginButtons on the login screen

Each item is either a component (renders into the slot via <RegistryYield>) or a menu item (a MenuItem-shaped object).

Registering Items via addon/extension.js

Every modern Fleetbase extension declares a single addon/extension.js that exports an object with a setupExtension(app, universe) hook. The console calls this hook once when the extension boots — this is where you register your contributions.

A minimal example:

// addon/extension.js
import { MenuItem, ExtensionComponent } from '@fleetbase/ember-core/contracts';

export default {
    setupExtension(app, universe) {
        const menuService = universe.getService('menu');

        // Add a settings entry inside Fleet-Ops
        menuService.registerMenuItem(
            'engine:fleet-ops',
            new MenuItem({
                title: 'My Plugin Config',
                component: new ExtensionComponent('@my-org/my-plugin-engine', 'my-plugin-settings'),
                icon: 'gear',
                slug: 'my-plugin',
                section: 'settings',
            })
        );
    },
};

The setupExtension hook receives:

  • app — the host Ember application instance
  • universe — the Universe service, your entry point to every registry

Available Universe Services

Look these up via universe.getService('<name>'):

ServicePurpose
menuRegister menu items, header menu items, settings menu items
registryCreate and read registries; register renderable components
widgetRegister dashboards and dashboard widgets
hookRegister lifecycle hooks (e.g. application:before-model)

Common Patterns

Add a Header Menu Item

// Register the extension's main entry in the console header
menuService.registerHeaderMenuItem('Storefront', 'console.storefront', {
    icon: 'store',
    priority: 1,
    description: 'Online store management.',
    shortcuts: [
        { title: 'Products', icon: 'box-open',     route: 'console.storefront.products' },
        { title: 'Orders',   icon: 'bag-shopping', route: 'console.storefront.orders'   },
    ],
});

Add a Settings Menu Item

menuService.registerSettingsMenuItem(
    new MenuItem({
        title: 'Customer Portal',
        icon: 'users-gear',
        slug: 'customer-portal',
        view: 'index',
        component: new ExtensionComponent('@fleetbase/customer-portal-engine', 'customer-portal-admin-settings'),
    })
);

Register a Component into a Slot

universe.registerRenderableComponent(
    'fleet-ops:template:settings:routing',
    new ExtensionComponent('@fleetbase/vroom-engine', 'organization/vroom-settings')
);

Create a New Registry

If your extension wants other extensions to plug into it, create the registry first:

const registryService = universe.getService('registry');
registryService.createRegistries(['my-extension:sidebar', 'my-extension:detail-tabs']);

Register a Hook

import { Hook } from '@fleetbase/ember-core/contracts';

const hookService = universe.getService('hook');
hookService.registerHook(
    new Hook('application:before-model', (session, router, transition) => {
        // run before the application route's model hook
    })
);

Wait for Another Engine to Load

Some setup needs to happen only after another engine has loaded — e.g. registering a service provider into Fleet-Ops's route-optimization engine:

universe.whenEngineLoaded('@fleetbase/fleetops-engine', async (fleetopsEngine, universe) => {
    const myEngine = await universe.extensionManager.ensureEngineLoaded('@my-org/my-engine');
    const routeOptimization = fleetopsEngine.lookup('service:route-optimization');
    const myProvider = myEngine.lookup('service:my-provider');
    routeOptimization.register('my-provider', myProvider);
});

Register a Dashboard or Widget

const widgetService = universe.getService('widget');
widgetService.registerDashboard('customer-portal');

The Contracts

@fleetbase/ember-core/contracts exports the canonical shapes you pass to the registries:

ContractPurpose
MenuItemMenu entries — { title, route, component, icon, slug, section, shortcuts, onClick, ... }
ExtensionComponentA reference to a component in another engine — new ExtensionComponent(engineName, componentName)
WidgetDashboard widget descriptor
HookLifecycle hook — new Hook(eventName, callback)

Rendering Items

Wherever a host extension wants to expose a slot, it uses <RegistryYield>:

<RegistryYield @registry="fleet-ops:component:vehicle:details" @context={{this.vehicle}} as |Component ctx|>
  <Component @vehicle={{ctx}} />
</RegistryYield>

See <RegistryYield> for the full component reference.

Real-World Examples

These open-source extensions all use addon/extension.js to register their contributions — read them as reference:

ExtensionWhat it registersaddon/extension.js
Customer PortalHooks, registries, login menu item, settings menu itemcustomer-portal/addon/extension.js
StorefrontHeader menu item with shortcuts, dashboard widgetsstorefront/addon/extension.js
VROOMRenderable settings components, route-optimization provider via whenEngineLoadedvroom/addon/extension.js
FlespiSettings menu, map drawer items, vehicle-detail panels, context-menu commandsflespi/addon/extension.js
LedgerInvoice template context, settings panelsledger/addon/extension.js

See Also

Overview | Fleetbase