Skip to main content

Plugin SDK API Reference

Complete API reference for @framework-m/plugin-sdk and @framework-m/vite-plugin.

@framework-m/plugin-sdk

Types

FrameworkMPlugin

interface FrameworkMPlugin {
name: string;
version: string;
minSdkVersion?: string;
peerPlugins?: string[];
manifests?: NavigationManifest[];
routes?: RouteDefinition[];
services?: Record<string, ServiceFactory>;
providers?: Provider[];
doctypes?: DocTypeExtension[];
ownedDocTypes?: string[];
widgets?: Widget[];
pages?: GlobalPageExtension[];
traversals?: Record<string, (...args: unknown[]) => unknown>;
discoveryUnit?: DiscoveryUnit;
onInit?: () => Promise<void> | void;
onDestroy?: () => Promise<void> | void;
}
interface NavigationManifest {
app_id: string;
label: string;
icon: string;
resources: NavigationNode[];
}
interface NavigationNode {
name: string;
label: string;
type?: "DocType" | "Page" | "Report" | "Kiosk" | "Link";
route: string;
icon?: string;
feature_id?: string;
visibility_policy?: VisibilityPolicy;
badge?: string;
hardware_intent?: string;
interaction_mode?: string;
workspace_route?: string;
children?: NavigationNode[];
hidden?: boolean;
permissions?: string[];
}

VisibilityPolicy

interface VisibilityPolicy {
roles?: string[];
attributes?: Record<string, unknown>;
feature_flag?: string;
}

RouteDefinition

interface RouteDefinition {
path: string;
element:
| LazyExoticComponent<ComponentType>
| (() => Promise<{ default: ComponentType }>);
/** Render outside the authentication guard when true */
isPublic?: boolean;
}

ServiceFactory

type ServiceFactory<T = unknown> = () => T | Promise<T>;

PermissionChecker

type PermissionChecker = (permissions: string[]) => boolean | Promise<boolean>;

Provider

interface Provider {
component: () => Promise<{ default: ComponentType }>;
props?: Record<string, unknown>;
}

DocTypeExtension

interface DocTypeExtension {
doctype: string;
fields?: Record<string, unknown>;
actions?: Array<Record<string, unknown>>;
components?: Record<string, ComponentType>;
}

Widget

interface Widget {
id: string;
title: string;
component: () => Promise<{ default: ComponentType }>;
size?: "small" | "medium" | "large";
permissions?: string[];
}

GlobalPageExtension

interface GlobalPageExtension {
name: string;
component: ComponentType;
}

DiscoveryUnit

Remote discovery metadata used in macroservice deployments.

interface DiscoveryUnit {
url: string;
apiUrl: string;
metaUrl: string;
openapiUrl?: string;
}

Classes

PluginRegistry

class PluginRegistry {
/** Access the global registry singleton */
static getInstance(): PluginRegistry;

/** Register a plugin configuration */
register(plugin: FrameworkMPlugin): Promise<void>;

/** Unregister a plugin by name */
unregister(pluginName: string): Promise<boolean>;

/** Register a resource owner hint for API namespacing */
registerOwner(resource: string, owner: string | null): void;

/**
* Resolve a standard `/api/...` URL to a service-specific namespace.
* In macroservice mode this prefixes the URL with the owning plugin's
* service name or discovery unit.
*/
resolveApiUrl(url: string): string;

/** Determine which plugin owns a specific resource */
getOwner(resourceName: string): string | null | undefined;

/** Get merged navigation manifests from all plugins */
getApps(): NavigationManifest[];

/** Get aggregated route definitions from all plugins */
getRoutes(): RouteDefinition[];

/** Get a specific plugin by name */
getPlugin(name: string): FrameworkMPlugin | undefined;

/** Get all registered plugins */
getAllPlugins(): FrameworkMPlugin[];

/** Get the plugin that owns a given resource */
getResourcePlugin(resourceName: string): FrameworkMPlugin | undefined;

/** Resolve a named service from the registry DI container */
getService<T = unknown>(name: string): Promise<T>;

/** Get the DI service container */
getServiceContainer(): ServiceContainer;

/** Aggregate dashboard widgets from all plugins */
getWidgets(): Widget[];

/** Configure a permission checker */
setPermissionChecker(checker: PermissionChecker | null): void;
getPermissionChecker(): PermissionChecker | null;

/** Compatibility and diagnostics */
checkCompatibility(): CompatibilityReport[];
getDiagnostics(severity?: PluginRegistryDiagnosticSeverity): PluginRegistryDiagnostic[];
clearDiagnostics(): void;

/** Subscribe to plugin lifecycle events */
on<E extends PluginRegistryEvent>(event: E, handler: PluginRegistryEventHandler<E>): () => void;

/** Reset registry state for test isolation */
reset(): void;
}

ServiceContainer

class ServiceContainer {
register<T>(name: string, factory: ServiceFactory<T>): void;
get<T = unknown>(name: string): Promise<T>;
has(name: string): boolean;
getAll(): string[];
clear(): void;
}

React Context and Hooks

PluginRegistryProvider

import {
PluginRegistryProvider,
PluginRegistry,
} from "@framework-m/plugin-sdk";

const registry = new PluginRegistry();
await registry.register(wmsPlugin);

<PluginRegistryProvider registry={registry}>
<App />
</PluginRegistryProvider>;

usePluginApps()

import { usePluginApps } from "@framework-m/plugin-sdk";

function Sidebar() {
const apps = usePluginApps();
return apps.map(app => (
<section key={app.app_id}>
<h3>{app.label}</h3>
{app.resources.map(node => (
<div key={node.name}>{node.label}</div>
))}
</section>
));
}

usePlugin(name)

import { usePlugin } from "@framework-m/plugin-sdk";

function PluginInfo() {
const wms = usePlugin("wms");
return wms ? (
<span>
{wms.name} v{wms.version}
</span>
) : null;
}

useService(name)

import { useService } from "@framework-m/plugin-sdk";

function StockLevel() {
const { service, isLoading, error } = useService("inventoryService");
if (isLoading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return <div>{service.getStockLevel()}</div>;
}

useWidgets()

import { useWidgets } from "@framework-m/plugin-sdk";

function Dashboard() {
const widgets = useWidgets();
return widgets.map(widget => <WidgetHost key={widget.id} widget={widget} />);
}

@framework-m/vite-plugin

frameworkMPlugin(options?)

import { frameworkMPlugin } from "@framework-m/vite-plugin";

export default defineConfig({
plugins: [react(), frameworkMPlugin()],
});

What it does:

  1. Workspace Scanning: Automatically traverses the workspace to find package.json files containing "framework-m": { "plugin": "..." }.
  2. Selective Bundling:
    • If the local package.json identifies as a Plugin, it only bundles the local plugin code.
    • If the local package is a Shell App, it bundles all discovered plugins from the workspace.
  3. Virtual Modules: Generates virtual:framework-m-plugins to aggregate discovered plugins.
  4. MFE Configuration: Injects FRAMEWORK_M_SERVICE_NAME via define for runtime identity.

Virtual Module Output

// virtual:framework-m-plugins
import plugin_0 from "/path/to/apps/wms/frontend/src/plugin.config.ts";
import plugin_1 from "/path/to/apps/hr/frontend/src/plugin.config.ts";
export default [plugin_0, plugin_1];

Backend Plugin Extension APIs

In addition to frontend widgets and pages, backend services can be customized or extended by overriding protocol dependencies in the DI container.

OAuthClaimMapper

Use the OAuthClaimMapper interface to intercept and parse token claim payloads from external OIDC identity providers during federated or social login callback exchanges.

Interface Signature

from typing import Any
from framework_m_core.interfaces.oauth import OAuth2UserInfo

class OAuthClaimMapper:
"""Interface for custom OIDC claims mapping."""

async def map_claims(self, user_info: OAuth2UserInfo) -> dict[str, Any]:
"""Map raw provider claims to LocalUser model attribute names.

Args:
user_info: The normalized OAuth2UserInfo details, containing
the provider name and raw_claims dict.

Returns:
A dictionary of LocalUser attribute names -> mapping values.
"""
pass

OAuth2UserInfo

Normalized container payload representing user metadata fetched from OIDC /userinfo or token payload claims.

Model Attributes

from dataclasses import dataclass

@dataclass(frozen=True)
class OAuth2UserInfo:
provider: str # OAuth provider name (e.g. google, oidc-main)
provider_user_id: str # Unique provider subject ID (e.g. sub or custom mapped oid)
email: str | None # Email address returned by the provider
display_name: str # Normalized display name of the user
avatar_url: str | None = None # Avatar image URL (if available)
phone_number: str | None = None # Verified phone number claim (if available)
phone_verified: bool = False # Whether the phone number is verified
raw_claims: dict[str, Any] = None # Dictionary containing all raw OIDC token claims

Lifecycle & Diagnostics

The registry emits events and diagnostics during registration.

type PluginRegistryEvent = "plugin:registered" | "plugin:error";
type PluginRegistryDiagnosticSeverity = "info" | "warning" | "error";

Common diagnostic codes:

CodeSeverityMeaning
SDK_INCOMPATIBLEwarningminSdkVersion is newer than the current SDK.
PLUGIN_DUPLICATEwarningPlugin name already registered and will be replaced.
ROUTE_COLLISIONerrorDuplicate route path across plugins.
MANIFEST_APP_ID_COLLISIONinfo/warningMultiple plugins contribute to the same app_id.
NODE_ROUTE_COLLISIONerrorDuplicate navigation node route across plugins.
SERVICE_COLLISIONerrorDuplicate service name across plugins.

Minimal Plugin Example

// apps/my-plugin/frontend/plugin.config.ts
import type { FrameworkMPlugin } from "@framework-m/plugin-sdk";

const plugin: FrameworkMPlugin = {
name: "my-plugin",
version: "1.0.0",
manifests: [
{
app_id: "my-plugin",
label: "My Plugin",
icon: "box",
resources: [
{
name: "my-plugin.item",
label: "Items",
route: "/app/my-plugin.item/list",
type: "DocType",
},
],
},
],
routes: [
{
path: "/my-plugin/dashboard",
element: () => import("./pages/Dashboard"),
},
],
};

export default plugin;

See Also