Skip to content

Frontend UI Mount Integration Guide

This guide explains how a frontend shell application integrates with the MetaOne platform to dynamically load and render extension UIs.


Overview

Extensions declare their UI through a manifest at install time. The platform stores these declarations and exposes them via a UI Mount API. The frontend shell queries this API on login, then uses the response to build navigation, register routes, and render widgets — without any hard-coded knowledge of which extensions exist.

Extension UI assets (JavaScript bundles) are served by the platform itself and require authentication, so the browser never needs direct access to the extension's origin.


Platform API Contract

Extensions are pure UI shells. They do not hold authentication tokens, construct backend URLs, or make HTTP calls. Instead, the host shell creates a platform object from its own auth context and passes it as a prop when mounting each remote component.

The platform prop

Every remote module (route page, widget) receives a single platform prop with the following shape:

FieldTypeDescription
workspaceIdstringCurrent workspace identifier
invokeCapability(key, payload?)(string, object?) → Promise<any>Calls a registered extension capability
publishEvent(eventKey, payload?)(string, object?) → Promise<void>Publishes a platform event

The extension never stores tokens, constructs API URLs, or uses fetch directly. All backend communication flows through the platform prop.

How the host constructs platform

The host shell creates the platform object using its own session state (current user token, selected workspace). A minimal implementation:

  • invokeCapability posts to POST /api/workspaces/{workspaceId}/capabilities/{key} with the JWT in the Authorization header.
  • publishEvent posts to POST /api/workspaces/{workspaceId}/events/publish with the event key and payload.

The extension never sees any of this. It simply calls platform.invokeCapability('crm.customer.search') and receives the resolved data.

When to use each method

  • Use invokeCapability for queries and commands: load a list, create a record, search, fetch details.
  • Use publishEvent for side-effect events: user sent a message, a deal changed stage, a notification should be dispatched. Events may trigger reactions in other extensions.

Step 1 — Fetch UI Mounts

After the user logs in and a workspace is selected, call the UI mounts bundle endpoint. This single call returns all navigation items, routes, and widgets registered by every enabled extension in that workspace.

Endpoint: GET /api/catalog/workspaces/{workspaceId}/ui-mounts

The response contains three lists:

  • nav — items to render in the sidebar or top navigation bar. Each item has a label, icon name, target route, and display order.
  • routes — URL path patterns the shell must register. Each entry maps a path pattern to an extension key.
  • widgets — injectable components identified by a named mount point. Each entry has an extension key, a mount point name, and a display order.

You can also fetch each list individually if needed:

  • GET .../ui-mounts/nav
  • GET .../ui-mounts/routes
  • GET .../ui-mounts/widgets — accepts an optional mountPoint query parameter to filter

Step 2 — Derive Asset URLs

The API response never includes asset URLs directly. The frontend constructs them from the extension key using a fixed pattern:

/api/catalog/workspaces/{workspaceId}/ui-mounts/{extensionKey}/assets/{filename}

The entry point for Module Federation is always remoteEntry.js, so the URL to load an extension's remote container is:

/api/catalog/workspaces/{workspaceId}/ui-mounts/{extensionKey}/assets/remoteEntry.js

The platform returns 403 if the extension is not installed and enabled in the requested workspace, so the URL itself is safe to use as a capability check.


Step 3 — Load Remote Modules

The platform uses Vite Module Federation (@originjs/vite-plugin-federation). Each extension publishes a remoteEntry.js that exposes named modules. The shell loads these at runtime via dynamic import.

Each extension exposes at least ./App — the main page component loaded when the user navigates to the extension's route. Extensions that register widgets expose additional named modules, one per widget.

The exposed module names for each currently registered extension are:

ExtensionExposesUsed for
crm-extension./AppRoute /ext/crm-demo/*
crm-extension./CrmDashboardWidgetMount point crm.dashboard.widget
chat-extension./AppRoute /ext/chat/*

When adding support for a new extension, check its manifest or ask the extension author which module names it exposes.


Step 4 — Register Routes Dynamically

For each entry in the routes list, register a route in the frontend router after fetching the mounts. The path patterns use /* wildcards; translate these to your router's syntax (Vue Router uses :pathMatch(.*)*).

Each route should lazily load the extension's ./App module using the asset URL derived in Step 2. This ensures the extension bundle is only fetched when the user navigates to that route.

When mounting the route component, pass the platform prop so the extension can interact with the backend.


Step 5 — Render Navigation

Render nav items from the nav list in display order (ascending by order). Each item provides:

  • label — display text
  • icon — icon identifier (use your icon library's lookup)
  • route — the path to navigate to when clicked
  • order — sort position among all extension nav items

Shell-defined nav items (home, settings, etc.) are static; extension nav items slot in alongside them.


Step 6 — Render Widget Mount Points

Place a widget slot component in any view where extensions can inject content. The slot is identified by a mount point name — a string like crm.dashboard.widget or customer.profile.tab.

At runtime, the slot queries the widgets list for entries matching its mount point name, sorts them by order, and renders each one by loading the corresponding named module from the extension's remote container.

Pass the platform prop to each widget component so it can load data and react to user interactions.

The shell does not need to know in advance which extensions will fill a slot. If no extension registers for a mount point, the slot renders nothing.


Refresh Behavior

UI mounts should be re-fetched whenever:

  • The user switches to a different workspace
  • The user installs or enables an extension (the admin flow should trigger a refresh)
  • The session is restored after a page reload

Mounts do not change during a normal session unless an admin action occurs. Polling is not required.


Error Handling

ScenarioExpected behavior
Extension asset returns 403Extension is not enabled in this workspace; skip loading
Extension asset returns 404Asset file missing in plugin directory; show error or skip
Remote module fails to loadCatch the dynamic import error; render a fallback or nothing
UI mounts endpoint returns empty listsNo extensions installed; render shell only

Currently Registered Extensions

Extension keyNav labelNav routeWidget mount point
crm-extensionCRM/ext/crm-democrm.dashboard.widget
chat-extensionChat/ext/chat(none)

MetaOne Platform Documentation