### Install Frontend Dependencies Source: https://fleetbase.io/docs/extension-development/getting-started/quickstart Install dependencies in the console directory to finalize the frontend link. ```bash cd fleetbase/console pnpm install ``` -------------------------------- ### Install Backend Dependencies Source: https://fleetbase.io/docs/extension-development/getting-started/quickstart Run composer install and the deployment script inside the application container to register the linked package. ```bash docker compose exec application composer install docker compose exec application bash -c "./deploy.sh" ``` -------------------------------- ### Implement addon/extension.js Source: https://fleetbase.io/docs/extension-development/universe/overview A comprehensive example demonstrating how to register menu items, components, widgets, and hooks using sub-services within the setupExtension method. ```javascript // addon/extension.js import { MenuItem, Widget, ExtensionComponent, Hook } from '@fleetbase/ember-core/contracts'; export default { setupExtension(app, universe) { const menuService = universe.getService('menu'); const registryService = universe.getService('registry'); const widgetService = universe.getService('widget'); const hookService = universe.getService('hook'); // 1. Top-level header item menuService.registerHeaderMenuItem('My Extension', 'console.my-extension', { icon: 'puzzle-piece', description: 'My custom logistics feature.', }); // 2. Settings item menuService.registerSettingsMenuItem( new MenuItem({ title: 'My Extension', slug: 'my-extension', icon: 'gear', component: new ExtensionComponent('@my-org/my-extension-engine', 'settings'), }) ); // 3. Inject a component into another extension's slot registryService.registerRenderableComponent( 'fleet-ops:component:order:details', new ExtensionComponent('@my-org/my-extension-engine', 'my-order-tab') ); // 4. Register a dashboard widget widgetService.registerWidgets('dashboard', [ new Widget({ id: 'my-extension-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 }, }), ]); // 5. Hook into the console boot hookService.registerHook( new Hook('console:after-model', (session, router) => { // do something after the user is authenticated }) ); }, }; ``` -------------------------------- ### Build Frontend Addon Source: https://fleetbase.io/docs/extension-development/getting-started/quickstart Install dependencies within the extension directory to prepare the addon for linking. ```bash cd packages/my-extension pnpm install ``` -------------------------------- ### Implement Application Route Source: https://fleetbase.io/docs/extension-development/frontend/routing Handles engine-level setup tasks that require access to the engine instance, such as theme configuration or session management. ```javascript // addon/routes/application.js import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; export default class ApplicationRoute extends Route { @service theme; @service session; activate() { this.theme.setRoutebodyClassNames(['my-extension']); } } ``` -------------------------------- ### Reference LedgerServiceProvider Path Source: https://fleetbase.io/docs/extension-development/backend/service-provider Example path for a concrete service provider implementation. ```text ledger/server/src/Providers/LedgerServiceProvider.php ``` -------------------------------- ### Install Fleetbase CLI Source: https://fleetbase.io/docs/extension-development/getting-started/quickstart Install the Fleetbase CLI globally via npm to enable extension scaffolding commands. ```bash npm install -g @fleetbase/cli ``` -------------------------------- ### Wait for extension boot in a route Source: https://fleetbase.io/docs/extension-development/universe/extension-manager Use this pattern in route hooks to ensure all extensions have finished their setup before proceeding with route logic. ```javascript import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; export default class ApplicationRoute extends Route { @service('universe/extension-manager') extensionManager; async beforeModel() { await this.extensionManager.waitForBoot(); } } ``` -------------------------------- ### Table Naming Convention Source: https://fleetbase.io/docs/extension-development/backend/migrations Example of creating a table with an extension-specific prefix. ```php Schema::create('my_extension_widgets', function (Blueprint $table) { ... }); ``` -------------------------------- ### Implement setupExtension Registration Logic Source: https://fleetbase.io/docs/extension-development/architecture/extension-registration Demonstrates registering menu items, dashboard widgets, registries, and lifecycle hooks using the universe service. ```javascript 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) => { // … }) ); }, }; ``` -------------------------------- ### waitForBoot() Source: https://fleetbase.io/docs/extension-development/universe/extension-manager Returns a promise that resolves when all setupExtension calls have finished. ```APIDOC ## waitForBoot() ### Description Resolves when all `setupExtension` calls have finished. Useful for pausing route execution until extensions are ready. ``` -------------------------------- ### Defining extension lifecycle hooks Source: https://fleetbase.io/docs/extension-development/universe/extension-manager Implement setupExtension for early boot tasks and onEngineLoaded for tasks requiring the engine instance. ```javascript export default { setupExtension(app, universe) { // synchronous setup — registries, menus, widgets }, onEngineLoaded(engine, universe, app) { // engine.lookup('service:my-service'), etc. }, }; ``` -------------------------------- ### Registering a frontend extension in addon/extension.js Source: https://fleetbase.io/docs/extension-development/architecture/overview Use the setupExtension method to register menu items or other console features during the application boot process. ```javascript import { MenuItem, ExtensionComponent } from '@fleetbase/ember-core/contracts'; export default { setupExtension(app, universe) { const menuService = universe.getService('menu'); menuService.registerHeaderMenuItem('My Extension', 'console.my-extension', { icon: 'puzzle-piece', description: 'My custom feature.', }); }, }; ``` -------------------------------- ### Registering an extension with UniverseService Source: https://fleetbase.io/docs/extension-development/universe/overview Use the setupExtension hook in addon/extension.js to access sub-services for menus, registries, widgets, and hooks. ```javascript // addon/extension.js import { MenuItem, ExtensionComponent } from '@fleetbase/ember-core/contracts'; export default { setupExtension(app, universe) { const menuService = universe.getService('menu'); const registryService = universe.getService('registry'); const widgetService = universe.getService('widget'); const hookService = universe.getService('hook'); // … register your contributions }, }; ``` -------------------------------- ### Resolving the Hook Service Source: https://fleetbase.io/docs/extension-development/universe/hook-service Access the hook service from the universe service within an extension's setup method. ```javascript // addon/extension.js import { Hook } from '@fleetbase/ember-core/contracts'; export default { setupExtension(app, universe) { const hookService = universe.getService('hook'); // … }, }; ``` -------------------------------- ### Querying Models via Ember Data Store Source: https://fleetbase.io/docs/extension-development/recipes/connecting-models-to-your-api Example of using the store service within an Ember Route to query records. ```javascript import { inject as service } from '@ember/service'; export default class WidgetsRoute extends Route { @service store; model() { return this.store.query('widget', { status: 'active' }); } } ``` -------------------------------- ### Visualize extension boot lifecycle Source: https://fleetbase.io/docs/extension-development/architecture/overview This diagram illustrates the boot sequence for both the console and the Laravel backend when an extension is loaded. ```text Console boots ├── ExtensionManager loads each enabled extension │ └── For each, dynamic-imports addon/extension.js │ └── runs setupExtension(app, universe) │ └── On first navigation to an extension route: └── Ember loads addon/engine.js (lazy) └── onEngineLoaded fires (if exported) Laravel boots └── ServiceProvider.boot() runs ├── parent::boot() — middleware groups, abilities ├── registerObservers() — model observers ├── registerExpansionsFrom() — class expansions ├── loadRoutesFrom() — server/src/routes.php └── loadMigrationsFrom() — server/migrations/ ``` -------------------------------- ### Merge Configuration in Service Provider Source: https://fleetbase.io/docs/extension-development/recipes/adding-a-sms-provider Use the boot method to merge the extension configuration into the main application services. ```php public function boot() { parent::boot(); $this->mergeConfigFrom(__DIR__ . '/../../config/services.php', 'services'); } ``` -------------------------------- ### Extension Boot Process Source: https://fleetbase.io/docs/extension-development/getting-started/extension-anatomy Visual representation of the boot sequence for both the console application and the Laravel backend. ```text Console application boots ├── ExtensionManager loads each extension's addon/extension.js │ └── setupExtension(app, universe) runs │ ├── menu service registers your header item, settings panels, etc. │ ├── widget service registers your dashboard widgets │ └── registry service creates registries / injects components │ └── On first navigation into your extension: └── Ember loads addon/engine.js ├── routes.js wires your route map └── onEngineLoaded(engineInstance, universe, app) fires (if exported) Laravel boots └── ServiceProvider.boot() runs ├── parent::boot() — middleware groups, abilities schemas ├── registerObservers() — attach your model observers ├── registerExpansionsFrom() — auto-load class expansions ├── loadRoutesFrom('server/src/routes.php') └── loadMigrationsFrom('server/migrations') ``` -------------------------------- ### Initialize Menu Service in Extension Source: https://fleetbase.io/docs/extension-development/universe/menu-service Access the menu service within the setupExtension hook of your extension configuration. ```javascript // addon/extension.js import { MenuItem, ExtensionComponent } from '@fleetbase/ember-core/contracts'; export default { setupExtension(app, universe) { const menuService = universe.getService('menu'); // … registrations }, }; ``` -------------------------------- ### Constructing ExtensionComponent objects Source: https://fleetbase.io/docs/extension-development/reference/contracts Shows the three ways to instantiate an ExtensionComponent: via string path, component class, or a full options object. ```javascript // Form 1: string component path inside an engine new ExtensionComponent('@fleetbase/storefront-engine', 'storefront-order-summary'); // Form 2: a component class directly import MyComponent from '../components/my-component'; new ExtensionComponent('@my-org/my-engine', MyComponent); // Form 3: full options object new ExtensionComponent('@my-org/my-engine', { path: 'components/my-component', loadingComponent: 'spinner', errorComponent: 'error-fallback', }); ``` -------------------------------- ### Register an extension menu item Source: https://fleetbase.io/docs/extension-development Use the setupExtension hook in addon/extension.js to register custom menu items with the UniverseService. ```javascript // addon/extension.js import { MenuItem, ExtensionComponent } from '@fleetbase/ember-core/contracts'; export default { setupExtension(app, universe) { const menuService = universe.getService('menu'); menuService.registerHeaderMenuItem('My Extension', 'console.my-extension', { icon: 'puzzle-piece', description: 'My custom logistics feature.', }); }, }; ``` -------------------------------- ### Publish Extension via CLI Source: https://fleetbase.io/docs/extension-development/publishing/overview Navigate to the extension root directory and execute the publish command to trigger manifest validation and the build process. ```bash cd my-extension flb publish ``` -------------------------------- ### Scaffold a New Extension Source: https://fleetbase.io/docs/extension-development/getting-started/quickstart Navigate to the packages directory and run the scaffold command to generate the extension skeleton. ```bash cd /path/to/fleetbase/packages flb scaffold my-extension ``` -------------------------------- ### Define a Service Provider for an Extension Source: https://fleetbase.io/docs/extension-development/getting-started/extension-anatomy Extends CoreServiceProvider to register observers, expansions, routes, and migrations during the boot process. ```php registerObservers(); $this->registerExpansionsFrom(__DIR__ . '/../Expansions'); $this->loadRoutesFrom(__DIR__ . '/../routes.php'); $this->loadMigrationsFrom(__DIR__ . '/../../migrations'); } } ``` -------------------------------- ### Transitioning via UniverseService Source: https://fleetbase.io/docs/extension-development/frontend/virtual-routes Utilize the transitionMenuItem helper on UniverseService to handle transitions while maintaining section, slug, and view query parameters. ```javascript universe.transitionMenuItem('console.settings.virtual', menuItem); ``` -------------------------------- ### Configure Hook Options Source: https://fleetbase.io/docs/extension-development/universe/hook-service Demonstrates how to chain configuration methods like priority, execution frequency, and identifiers when initializing a new hook. ```javascript new Hook('application:before-model') .execute((session, router) => { /* … */ }) .withPriority(20) .once() .withId('my-extension:bootstrap'); ``` -------------------------------- ### whenEngineLoaded(engineName, callback) Source: https://fleetbase.io/docs/extension-development/universe/extension-manager Registers a callback to be executed once a specific engine has finished booting. If the engine is already loaded, the callback is executed immediately. ```APIDOC ## whenEngineLoaded(engineName, callback) ### Description Run a callback once the named engine boots. If the engine is already loaded, the callback runs immediately. ### Parameters - **engineName** (string) - Required - The name of the engine to wait for. - **callback** (function) - Required - The function to execute. Signature: (engineInstance, universe, applicationInstance) => void | Promise. ### Example ```javascript universe.whenEngineLoaded('@fleetbase/fleetops-engine', this.registerVroom); ``` ``` -------------------------------- ### Access the Registry Service Source: https://fleetbase.io/docs/extension-development/universe/registry-service Retrieve the registry service instance within the setupExtension hook of an extension. ```javascript // addon/extension.js import { ExtensionComponent } from '@fleetbase/ember-core/contracts'; export default { setupExtension(app, universe) { const registryService = universe.getService('registry'); // … }, }; ``` -------------------------------- ### Register extension entry point Source: https://fleetbase.io/docs/extension-development/getting-started/extension-anatomy The addon/extension.js file is the registration entry point used to register menu items, widgets, and hooks. ```javascript // addon/extension.js import { MenuItem, ExtensionComponent } from '@fleetbase/ember-core/contracts'; export default { setupExtension(app, universe) { const menuService = universe.getService('menu'); menuService.registerHeaderMenuItem('My Extension', 'console.my-extension', { icon: 'puzzle-piece', description: 'My custom feature.', }); }, }; ``` -------------------------------- ### Registering a Payment Gateway Driver Source: https://fleetbase.io/docs/extension-development/recipes/adding-a-payment-gateway-driver Use the boot method in your service provider to extend the PaymentGatewayManager, ensuring the driver is registered safely. ```php app->afterResolving(PaymentGatewayManager::class, function (PaymentGatewayManager $manager) { $manager->extend('paypal', fn ($app) => $app->make(PaypalDriver::class)); }); } ``` -------------------------------- ### Register expansions in ServiceProvider Source: https://fleetbase.io/docs/extension-development/backend/expansions-and-observers Use registerExpansionsFrom in the boot method to automatically load expansions from a directory. ```php // server/src/Providers/MyExtensionServiceProvider.php public function boot() { parent::boot(); $this->registerExpansionsFrom(__DIR__ . '/../Expansions'); } ``` -------------------------------- ### Standard Migration Skeleton Source: https://fleetbase.io/docs/extension-development/backend/migrations Use this template for creating new organization-scoped resources. Includes required columns for multi-tenancy, soft deletes, and timestamps. ```php increments('id'); $table->char('uuid', 36)->nullable()->unique(); // primary identifier $table->string('public_id')->nullable()->unique(); // user-visible ID (e.g. "widget_a1b2c3d4") $table->char('company_uuid', 36)->nullable()->index(); // org scoping — required for shared infra $table->char('created_by_uuid', 36)->nullable()->index(); $table->string('name'); $table->text('description')->nullable(); $table->json('meta')->nullable(); $table->string('status')->default('active'); $table->softDeletes(); // adds deleted_at $table->timestamps(); // adds created_at, updated_at }); } public function down(): void { Schema::dropIfExists('my_extension_widgets'); } }; ``` -------------------------------- ### Define Extension Registration Hooks Source: https://fleetbase.io/docs/extension-development/architecture/extension-registration The object form allows defining both setupExtension and onEngineLoaded hooks in a single file. ```javascript // 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) }, }; ``` -------------------------------- ### Using the app-cache service Source: https://fleetbase.io/docs/extension-development/reference/ember-services Demonstrates storing and retrieving cross-session preferences using the appCache service. Use a colon-prefixed namespace on keys to avoid collisions. ```javascript @service appCache; setLastTab(tab) { this.appCache.set('my-extension:last-tab', tab); } get lastTab() { return this.appCache.get('my-extension:last-tab', 'overview'); } ``` -------------------------------- ### Configure Frontend Addon Link Source: https://fleetbase.io/docs/extension-development/getting-started/quickstart Add the extension as a link dependency in the console package.json file. ```json { "dependencies": { "@my-org/my-extension-engine": "link:../packages/my-extension" } } ``` -------------------------------- ### Constructing Hook objects Source: https://fleetbase.io/docs/extension-development/reference/contracts Demonstrates creating hooks using direct arguments, an options object, or a fluent chaining API. ```javascript new Hook('console:after-model', (session, router) => { // … }); // With options new Hook({ name: 'application:before-model', handler: (session, router, transition) => { /* … */ }, priority: 10, once: false, enabled: true, }); // Chaining new Hook('console:before-model') .execute((session, router) => { /* … */ }) .withPriority(20) .once(); ``` -------------------------------- ### Initialize a Registry Source: https://fleetbase.io/docs/extension-development/reference/contracts Creates a new named registry instance for manipulation or namespacing. ```javascript new Registry('my-extension:sidebar'); ``` -------------------------------- ### Registering a Dashboard and Widgets Source: https://fleetbase.io/docs/extension-development/universe/widget-service Initializes a new dashboard surface and attaches a collection of widgets to it. ```javascript widgetService.registerDashboard('ledger'); widgetService.registerWidgets('ledger', widgets); ``` -------------------------------- ### Create a Minimal Service Provider Source: https://fleetbase.io/docs/extension-development/backend/service-provider Defines a service provider class extending CoreServiceProvider to manage observers, commands, and package resources. ```php \MyOrg\MyExtension\Observers\WidgetObserver::class, ]; /** * Console commands auto-registered by CoreServiceProvider. */ public $commands = [ \MyOrg\MyExtension\Console\Commands\SyncCommand::class, ]; public function register() { $this->app->register(CoreServiceProvider::class); } public function boot() { $this->registerCommands(); $this->registerObservers(); $this->registerExpansionsFrom(__DIR__ . '/../Expansions'); $this->loadRoutesFrom(__DIR__ . '/../routes.php'); $this->loadMigrationsFrom(__DIR__ . '/../../migrations'); $this->mergeConfigFrom(__DIR__ . '/../../config/my-extension.php', 'my-extension'); } } ``` -------------------------------- ### Configure Backend Package Link Source: https://fleetbase.io/docs/extension-development/getting-started/quickstart Add the local path repository and require the extension package in the main API composer.json file. ```json { "repositories": [ { "type": "path", "url": "../packages/my-extension/server" } ], "require": { "my-org/my-extension-api": "*" } } ``` -------------------------------- ### registerSettingsMenuItem(itemOrTitle, options?) Source: https://fleetbase.io/docs/extension-development/universe/menu-service Adds an entry to the Settings page, which can render a component or transition to a specific route. ```APIDOC ## registerSettingsMenuItem(itemOrTitle, options?) ### Description Adds an entry to the Settings page. If a component is provided in the MenuItem, it renders within the settings page upon selection. ### Parameters - **itemOrTitle** (string|MenuItem) - Required - The title of the menu item or a MenuItem instance. - **options** (object) - Optional - Configuration options for the settings menu item. ``` -------------------------------- ### Implement a custom SMS provider class Source: https://fleetbase.io/docs/extension-development/recipes/adding-a-sms-provider Create a service class with a constructor for configuration and a public send method to handle SMS dispatching. ```php apiKey = config('services.messagebird.api_key', ''); $this->from = config('services.messagebird.from', ''); $this->baseUrl = config('services.messagebird.base_url', 'https://rest.messagebird.com'); } public function send(string $to, string $text, ?string $from = null): array { if (empty($this->apiKey)) { throw new \Exception('MessageBird API key not configured.'); } $response = Http::withHeaders([ 'Authorization' => "AccessKey {$this->apiKey}", ])->post("{$this->baseUrl}/messages", [ 'originator' => $from ?? $this->from, 'recipients' => [$to], 'body' => $text, ]); if (!$response->successful()) { Log::error('MessageBird send failed', [ 'status' => $response->status(), 'body' => $response->json(), 'to' => $to, ]); throw new \Exception('MessageBird SMS failed: ' . $response->body()); } return [ 'success' => true, 'provider' => 'messagebird', 'message_id' => $response->json('id'), 'response' => $response->json(), ]; } } ``` -------------------------------- ### Migration Directory Structure Source: https://fleetbase.io/docs/extension-development/backend/migrations The standard directory layout for extension migrations. ```text server/ └── migrations/ └── 2026_01_15_000000_create_widgets_table.php ``` -------------------------------- ### Execute Hooks Source: https://fleetbase.io/docs/extension-development/universe/hook-service Shows how to trigger hooks using both asynchronous and synchronous execution methods. ```javascript // Async — awaits all handlers in priority order const results = await hookService.execute('my-extension:order-imported', order, source); // Sync — for code that can't be async hookService.executeSync('my-extension:before-render', context); ``` -------------------------------- ### Constructing MenuItem Instances Source: https://fleetbase.io/docs/extension-development/reference/contracts Create menu items using either a title and route constructor or a full definition object. ```javascript // Form 1: title + route (plus optional chainables) new MenuItem('Fleet-Ops', 'console.fleet-ops') .withIcon('route') .withPriority(0) .withDescription('Manage fleets, drivers, and live orders'); // Form 2: full definition object new MenuItem({ title: 'Settings', slug: 'my-extension', icon: 'gear', component: new ExtensionComponent('@my-org/my-engine', 'settings'), }); ``` -------------------------------- ### Configure the Engine Class in addon/engine.js Source: https://fleetbase.io/docs/extension-development/frontend/routing Defines the engine class, imports host services, and sets up external route dependencies. ```javascript // addon/engine.js import Engine from '@ember/engine'; import loadInitializers from 'ember-load-initializers'; import Resolver from 'ember-resolver'; import config from './config/environment'; import { services, externalRoutes } from '@fleetbase/ember-core/exports'; const { modulePrefix } = config; export default class MyExtensionEngine extends Engine { modulePrefix = modulePrefix; Resolver = Resolver; dependencies = { services, externalRoutes, }; } loadInitializers(MyExtensionEngine, modulePrefix); ``` -------------------------------- ### Run Migrations via CLI Source: https://fleetbase.io/docs/extension-development/backend/migrations Commands for executing migrations within the development environment. ```bash docker compose exec application php artisan migrate ``` ```bash docker compose exec application php artisan migrate:fresh --seed ``` -------------------------------- ### Registering an engine with whenEngineLoaded Source: https://fleetbase.io/docs/extension-development/universe/extension-manager Use this pattern to wait for a specific engine to load before performing integration tasks like service registration. ```javascript // addon/extension.js — VROOM registering itself with Fleet-Ops export default { setupExtension(app, universe) { universe.whenEngineLoaded('@fleetbase/fleetops-engine', this.registerVroom); }, async registerVroom(fleetopsEngine, universe) { const vroomEngine = await universe.extensionManager.ensureEngineLoaded('@fleetbase/vroom-engine'); const routeOptimization = fleetopsEngine.lookup('service:route-optimization'); const vroom = vroomEngine.lookup('service:vroom'); if (routeOptimization && vroom) { routeOptimization.register('vroom', vroom); } }, }; ``` -------------------------------- ### Define extension manifest Source: https://fleetbase.io/docs/extension-development/getting-started/extension-anatomy The extension.json file provides metadata and paths to the package definitions. ```json { "name": "Ledger", "version": "0.0.3", "description": "Accounting & Invoicing Extension for Fleetbase", "repository": "https://github.com/fleetbase/ledger", "license": "AGPL-3.0-or-later", "author": "Fleetbase Pte Ltd ", "engine": "package.json", "api": "composer.json" } ``` -------------------------------- ### Define Extension API Routes Source: https://fleetbase.io/docs/extension-development/recipes/calling-your-extension-api Register your extension's API routes in server/src/routes.php to establish the routing prefix. ```php Route::prefix(config('my-extension.api.routing.prefix', 'my-extension')) ->namespace('MyOrg\MyExtension\Http\Controllers') ->group(function ($router) { $router->group(['middleware' => ['fleetbase.protected']], function ($router) { $router->fleetbaseRoutes('widgets'); }); }); ``` -------------------------------- ### Defining a backend service provider in server/src/Providers Source: https://fleetbase.io/docs/extension-development/architecture/overview Extend CoreServiceProvider to register observers, expansions, routes, and migrations for the backend package. ```php registerObservers(); $this->registerExpansionsFrom(__DIR__ . '/../Expansions'); $this->loadRoutesFrom(__DIR__ . '/../routes.php'); $this->loadMigrationsFrom(__DIR__ . '/../../migrations'); } } ``` -------------------------------- ### Creating a Fetch Wrapper Service Source: https://fleetbase.io/docs/extension-development/reference/ember-services Define a custom service to encapsulate namespace configuration for repeated calls to an extension's API. ```javascript // addon/services/my-extension-api.js import Service, { inject as service } from '@ember/service'; export default class MyExtensionApi extends Service { @service fetch; namespace = 'my-extension/v1'; get(path, query) { return this.fetch.get(path, query, { namespace: this.namespace }); } post(path, data) { return this.fetch.post(path, data, { namespace: this.namespace }); } // …etc } ``` -------------------------------- ### Implement a Payment Gateway Driver Source: https://fleetbase.io/docs/extension-development/recipes/adding-a-payment-gateway-driver Create a custom driver by extending AbstractGatewayDriver and implementing the required methods for payment processing and configuration. ```php 'client_id', 'label' => 'Client ID', 'type' => 'text', 'required' => true], ['key' => 'client_secret', 'label' => 'Client Secret', 'type' => 'password', 'required' => true], ]; } public function purchase(PurchaseRequest $request): GatewayResponse { $token = $this->getAccessToken(); $response = Http::withToken($token) ->baseUrl($this->host()) ->post('/v2/checkout/orders', [ 'intent' => 'CAPTURE', 'purchase_units' => [[ 'amount' => [ 'currency_code' => $request->currency, 'value' => sprintf('%.2f', $request->amount / 100), ], 'reference_id' => $request->reference, ]], ]) ->throw(); $data = $response->json(); return new GatewayResponse( success: true, reference: $data['id'], raw: $data, ); } public function refund(RefundRequest $request): GatewayResponse { $token = $this->getAccessToken(); $response = Http::withToken($token) ->baseUrl($this->host()) ->post("/v2/payments/captures/{$request->transactionReference}/refund", [ 'amount' => [ 'currency_code' => $request->currency, 'value' => sprintf('%.2f', $request->amount / 100), ], ]) ->throw(); return new GatewayResponse( success: true, reference: $response->json('id'), raw: $response->json(), ); } public function handleWebhook(Request $request): GatewayResponse { $event = $request->input('event_type'); return match ($event) { 'PAYMENT.CAPTURE.COMPLETED' => $this->handleCaptureCompleted($request), 'PAYMENT.CAPTURE.REFUNDED' => $this->handleRefundCompleted($request), default => GatewayResponse::ignored($event), }; } protected function host(): string { return $this->sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'; } protected function getAccessToken(): string { return Http::withBasicAuth($this->config['client_id'], $this->config['client_secret']) ->asForm() ->baseUrl($this->host()) ->post('/v1/oauth2/token', ['grant_type' => 'client_credentials']) ->json('access_token'); } protected function handleCaptureCompleted(Request $request): GatewayResponse { /* ... */ } protected function handleRefundCompleted(Request $request): GatewayResponse { /* ... */ } } ``` -------------------------------- ### Create Registries Source: https://fleetbase.io/docs/extension-development/universe/registry-service Initializes new registries to allow other extensions to plug into specific sections. ```javascript const registryService = universe.getService('registry'); registryService.createRegistries([ 'my-extension:sidebar', 'my-extension:order:tabs', 'my-extension:dashboard:widgets', ]); ``` -------------------------------- ### Testing a Payment Gateway Driver Source: https://fleetbase.io/docs/extension-development/recipes/adding-a-payment-gateway-driver Use the PaymentGatewayManager to instantiate a driver and execute a purchase request. ```php use Fleetbase\Ledger\PaymentGatewayManager; use Fleetbase\Ledger\DTO\PurchaseRequest; $manager = app(PaymentGatewayManager::class); $paypal = $manager->gateway('paypal'); $response = $paypal->purchase(new PurchaseRequest( amount: 5000, // cents currency: 'USD', reference: $invoice->public_id, )); ``` -------------------------------- ### Define Extension as a Function Source: https://fleetbase.io/docs/extension-development/architecture/extension-registration A simplified registration form that acts as an alias for the setupExtension hook. ```javascript // addon/extension.js export default function (app, universe) { // equivalent to setupExtension only } ``` -------------------------------- ### Registering a Vendor Descriptor in Service Provider Source: https://fleetbase.io/docs/extension-development/recipes/adding-an-integrated-vendor Append the vendor configuration array to IntegratedVendors::$supported within the boot method of your extension's service provider. ```php 'Acme Dispatch', 'code' => 'acme-dispatch', 'host' => 'https://api.acme-dispatch.com/', 'sandbox' => 'https://sandbox.acme-dispatch.com/', 'namespace' => 'v1', 'bridge' => AcmeDispatch::class, // Optional — list of credential fields the user fills out 'credentialParams' => [ ['key' => 'api_key'], ['key' => 'api_secret'], ], // Optional — additional config the user sets 'optionParams' => [ ['key' => 'market', 'options' => [ ['code' => 'us', 'key' => 'United States'], ['code' => 'mx', 'key' => 'Mexico'], ], 'optionValue' => 'code', 'optionLabel' => 'key'], ], // Maps user-saved values onto your bridge's constructor args 'bridgeParams' => [ 'apiKey' => 'credentials.api_key', 'apiSecret' => 'credentials.api_secret', 'sandbox' => 'sandbox', 'market' => 'options.market', ], // Lifecycle callbacks — methods to call on the bridge at vendor lifecycle events 'callbacks' => [ 'onCreated' => ['setWebhook' => ['webhook_url']], 'onUpdated' => ['setWebhook' => ['webhook_url']], 'onCanceled' => ['cancelFromFleetbaseOrder' => []], ], ]; } ``` -------------------------------- ### Create a Wrapper Service for API Calls Source: https://fleetbase.io/docs/extension-development/recipes/calling-your-extension-api Define a service to encapsulate the API namespace, reducing repetition when making multiple calls to the same extension. ```javascript // addon/services/my-extension-api.js import Service, { inject as service } from '@ember/service'; export default class MyExtensionApi extends Service { @service fetch; namespace = 'my-extension/v1'; get(path, query) { return this.fetch.get(path, query, { namespace: this.namespace }); } post(path, data) { return this.fetch.post(path, data, { namespace: this.namespace }); } put(path, data) { return this.fetch.put(path, data, { namespace: this.namespace }); } delete(path, data) { return this.fetch.delete(path, data, { namespace: this.namespace }); } upload(path, files) { return this.fetch.upload(path, files, { namespace: this.namespace }); } } ``` -------------------------------- ### registerServiceIntoAllEngines(serviceName, class, options?) Source: https://fleetbase.io/docs/extension-development/universe/extension-manager Registers a service into every currently loaded engine. ```APIDOC ## registerServiceIntoAllEngines(serviceName, class, options?) ### Description Registers a service into every loaded engine. Returns a list of engines where the registration succeeded. ### Parameters - **serviceName** (string) - Required - The name to register the service under. - **class** (Object) - Required - The service class to register. - **options** (Object) - Optional - Additional registration options. ``` -------------------------------- ### registerServiceIntoEngine(engineName, serviceName, class, options?) Source: https://fleetbase.io/docs/extension-development/universe/extension-manager Registers a service into a specific engine's DI container. ```APIDOC ## registerServiceIntoEngine(engineName, serviceName, class, options?) ### Description Registers a service into one specific engine. The target engine must be loaded before calling this method. ### Parameters - **engineName** (string) - Required - The name of the target engine. - **serviceName** (string) - Required - The name to register the service under. - **class** (Object) - Required - The service class to register. - **options** (Object) - Optional - Additional registration options. ``` -------------------------------- ### Register ReportSchemaServiceProvider Source: https://fleetbase.io/docs/extension-development/recipes/registering-a-report-schema Create a service provider that registers the report schema after the ReportSchemaRegistry is resolved to avoid boot-order issues. ```php callAfterResolving(ReportSchemaRegistry::class, function (ReportSchemaRegistry $registry) { (new MyExtensionReportSchema())->registerReportSchema($registry); }); } } ``` -------------------------------- ### registerComponentIntoEngine(engineName, componentName, class, options?) Source: https://fleetbase.io/docs/extension-development/universe/extension-manager Registers a component into a specific engine's DI container. ```APIDOC ## registerComponentIntoEngine(engineName, componentName, class, options?) ### Description Registers a component into one specific engine. The target engine must be loaded before calling this method. ### Parameters - **engineName** (string) - Required - The name of the target engine. - **componentName** (string) - Required - The name to register the component under. - **class** (Object) - Required - The component class to register. - **options** (Object) - Optional - Additional registration options. ``` -------------------------------- ### Register Observers in Service Provider Source: https://fleetbase.io/docs/extension-development/backend/expansions-and-observers Map models to their respective observer classes in the $observers array and call $this->registerObservers() within the boot method. ```php // server/src/Providers/MyExtensionServiceProvider.php public $observers = [ \MyOrg\MyExtension\Models\Widget::class => \MyOrg\MyExtension\Observers\WidgetObserver::class, // You can also observe core models — fires on the same model used by other extensions \Fleetbase\Models\User::class => \MyOrg\MyExtension\Observers\UserObserver::class, ]; public function boot() { parent::boot(); $this->registerObservers(); } ``` -------------------------------- ### Implement role-based gating Source: https://fleetbase.io/docs/extension-development/frontend/routing Use the abilities service to check permissions before entering a route. Permission strings follow the '{extension} {action} {resource}' format. ```javascript @service abilities; beforeModel() { if (!this.abilities.can('fleet-ops view order')) { return this.transitionToExternal('console'); } } ``` -------------------------------- ### Resolving the Widget Service Source: https://fleetbase.io/docs/extension-development/universe/widget-service Access the widget service instance within the setupExtension hook of an extension. ```javascript // addon/extension.js import { Widget, ExtensionComponent } from '@fleetbase/ember-core/contracts'; export default { setupExtension(app, universe) { const widgetService = universe.getService('widget'); // … }, }; ``` -------------------------------- ### Targeting Extension API with Fetch Source: https://fleetbase.io/docs/extension-development/reference/ember-services Use the namespace option to direct fetch requests to an extension-specific API path instead of the default console namespace. ```javascript // Hits /my-extension/widgets, not /int/v1/widgets this.fetch.get('widgets', {}, { namespace: 'my-extension' }); // With versioning this.fetch.post('widgets', { name: 'New' }, { namespace: 'my-extension/v1' }); ``` -------------------------------- ### Engine Directory Structure Source: https://fleetbase.io/docs/extension-development/frontend/templates-and-components Shows the standard file layout for engine templates and routes. ```text addon/ ├── routes.js # this.route('orders', { ... }) ├── routes/ │ └── orders.js ├── controllers/ │ └── orders.js └── templates/ ├── application.hbs # outermost layout for the engine └── orders.hbs # rendered for the orders route ``` -------------------------------- ### Implement common patterns with the current-user service Source: https://fleetbase.io/docs/extension-development/reference/ember-services Utilize the current-user service for permission gating, organization-scoped data fetching, and reacting to user state changes. ```javascript // Permission gating if (this.currentUser.permissions.includes('my-extension view widget')) { … } // Organization-scoped fetch this.fetch.get(`my-extension/widgets?company_id=${this.currentUser.companyId}`); // React to user changes this.events.subscribe('user:changed', () => { /* refresh local state */ }); ``` -------------------------------- ### Initialize a TemplateHelper Source: https://fleetbase.io/docs/extension-development/reference/contracts Constructs a reference to a helper, either lazy-loaded from an engine or provided as a direct class. ```javascript import TemplateHelper from '@fleetbase/ember-core/contracts/template-helper'; // Lazy from another engine new TemplateHelper('@fleetbase/storefront-engine', 'helpers/calculate-delivery-fee'); // Or a direct class new TemplateHelper(null, MyHelperClass); ``` -------------------------------- ### Register a settings menu item with a virtual component Source: https://fleetbase.io/docs/extension-development/frontend/virtual-routes Use registerSettingsMenuItem to add a link to the settings menu that renders a specific engine component. ```javascript // addon/extension.js import { MenuItem, ExtensionComponent } from '@fleetbase/ember-core/contracts'; export default { setupExtension(app, universe) { const menuService = universe.getService('menu'); // Adds /console/settings/my-extension that renders the engine component menuService.registerSettingsMenuItem( new MenuItem({ title: 'My Extension', icon: 'gear', slug: 'my-extension', view: 'index', component: new ExtensionComponent( '@my-org/my-extension-engine', 'my-extension-settings' ), }) ); }, }; ``` -------------------------------- ### Implement ReportSchema for an extension Source: https://fleetbase.io/docs/extension-development/recipes/registering-a-report-schema Create a class implementing the ReportSchema contract to register custom tables and define their schema properties. ```php registerTable($this->createWidgetsTable()); } protected function createWidgetsTable(): Table { return Table::make('my_extension_widgets') ->label('Widgets') ->description('Custom widgets created by My Extension') ->category('My Extension') ->extension('my-extension') ->excludeColumns(['uuid', 'deleted_at']) ->maxRows(50000) ->cacheTtl(3600) ->columns([ Column::make('public_id', 'string') ->label('Widget ID') ->description('User-facing widget identifier') ->searchable() ->filterable() ->sortable(), Column::make('name', 'string') ->label('Name') ->searchable() ->filterable() ->sortable(), Column::make('status', 'string') ->label('Status') ->filterable() ->sortable() ->aggregatable() ->transformer(fn ($value) => ucfirst($value)), Column::make('created_at', 'datetime') ->label('Created') ->filterable() ->sortable(), ]) ->relationships([ Relationship::belongsTo('company', 'companies', 'company_uuid', 'uuid') ->label('Organization'), Relationship::belongsTo('createdBy', 'users', 'created_by_uuid', 'uuid') ->label('Created By'), ]); } } ``` -------------------------------- ### Perform CRUD operations with the crud service Source: https://fleetbase.io/docs/extension-development/reference/ember-services Demonstrates using the crud service to handle record deletion and bulk deletion with lifecycle callbacks. ```javascript @service crud; @action removeWidget(widget) { this.crud.delete(widget, { title: `Delete ${widget.name}?`, onSuccess: () => this.refreshList(), onError: (err) => console.error(err), }); } @action removeAllSelected() { this.crud.bulkDelete(this.selected, { modelName: 'widget', onSuccess: () => this.clearSelection(), }); } ``` -------------------------------- ### registerService(name, ServiceClass, options?) Source: https://fleetbase.io/docs/extension-development/universe/registry-service Registers a service globally to make it available to all engines. ```APIDOC ## registerService(name, ServiceClass, options?) ### Description Make a service available to all engines by name. ### Parameters - **name** (String) - Required - The name to register the service under. - **ServiceClass** (Object) - Required - The service class to register. - **options** (Object) - Optional - Additional registration options. ``` -------------------------------- ### Register a Renderable Component Source: https://fleetbase.io/docs/extension-development/universe/registry-service Inject a component into a host extension's slot using the registerRenderableComponent method. ```javascript import { ExtensionComponent } from '@fleetbase/ember-core/contracts'; registryService.registerRenderableComponent( 'fleet-ops:component:order:details', new ExtensionComponent('@fleetbase/storefront-engine', 'storefront-order-summary') ); ``` -------------------------------- ### Register a settings menu item Source: https://fleetbase.io/docs/extension-development/universe/menu-service Adds an entry to the Settings page. If a component is provided, it renders inside the settings page upon selection. ```javascript menuService.registerSettingsMenuItem( new MenuItem({ title: 'My Extension', slug: 'my-extension', icon: 'gear', component: new ExtensionComponent('@my-org/my-engine', 'settings-panel'), }) ); ```