Plugin SDK API Reference
Complete API reference for
@framework-m/plugin-sdkand@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;
}
NavigationManifest
interface NavigationManifest {
app_id: string;
label: string;
icon: string;
resources: NavigationNode[];
}
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:
- Workspace Scanning: Automatically traverses the workspace to find
package.jsonfiles containing"framework-m": { "plugin": "..." }. - Selective Bundling:
- If the local
package.jsonidentifies 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.
- If the local
- Virtual Modules: Generates
virtual:framework-m-pluginsto aggregate discovered plugins. - MFE Configuration: Injects
FRAMEWORK_M_SERVICE_NAMEviadefinefor 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:
| Code | Severity | Meaning |
|---|---|---|
SDK_INCOMPATIBLE | warning | minSdkVersion is newer than the current SDK. |
PLUGIN_DUPLICATE | warning | Plugin name already registered and will be replaced. |
ROUTE_COLLISION | error | Duplicate route path across plugins. |
MANIFEST_APP_ID_COLLISION | info/warning | Multiple plugins contribute to the same app_id. |
NODE_ROUTE_COLLISION | error | Duplicate navigation node route across plugins. |
SERVICE_COLLISION | error | Duplicate 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
- Plugin System Guide — plugin architecture and auto-discovery.
- Package Frontend Structure Guide — package layout, build workflow, and publishing.
- Navigation Manifest Examples — focused examples for
manifestsand routes. - Tutorial: Building a Multi-Module App — end-to-end WMS + Personnel tutorial.
- Plugin-Host Composition Patterns — multi-package UI architecture and CI/CD patterns.