plugin

Stately Plugin System.

This module provides the infrastructure for creating UI plugins that extend the Stately runtime. Plugins can add:

  • API operations (typed fetch clients)
  • Utility functions
  • Navigation routes
  • Custom components for node types
  • Custom configuration options

For Most Users

Use statelyUi from @statelyjs/stately to create runtimes with the core plugin included, then add additional plugins via withPlugin():

Examples

import { statelyUi } from '@statelyjs/stately';
import { type FilesUiPlugin, filesUiPlugin } from '@statelyjs/files';

const runtime = statelyUi<MySchemas, readonly [FilesUiPlugin]>({
  schema,
  client,
  options
}).withPlugin(filesUiPlugin({ api: { pathPrefix: '/files' } }));

// Access plugin functionality
runtime.plugins.core.api.operations.list_entities(...); // Core plugin
runtime.plugins.files.api.operations.list_files(...);   // Files plugin

For Plugin Authors

Use createUiPlugin to build plugins with an ergonomic API:

  1. Define your plugin's type using DefineUiPlugin:
import type { DefineUiPlugin, DefineOptions } from '@statelyjs/ui';

export type MyPlugin = DefineUiPlugin<
  'my-plugin',           // Unique plugin name (must be string literal)
  MyPaths,               // OpenAPI paths type (generated from Rust OpenAPI)
  typeof MY_OPERATIONS,  // Operation bindings (generated from Rust OpenAPI)
  MyUtils,               // Utility functions type
  MyOptions              // Configuration options type
>;
  1. Create the plugin factory using createUiPlugin:
import { createUiPlugin } from '@statelyjs/ui';

export const myUiPlugin = createUiPlugin<MyPlugin>({
  name: 'my-plugin',
  operations: MY_OPERATIONS,
  routes: myDefaultRoutes,
  utils: myUtils,

  setup: (ctx, options) => {
    // Register custom components
    ctx.registerComponent('MyNodeType', 'edit', MyEditComponent);
    ctx.registerComponent('MyNodeType', 'view', MyViewComponent);

    // Extend extension points
    someExtension.extend(myTransformer);

    return {};
  },
});

The createUiPlugin helper provides:

  • No manual spreading - Return only what's being added
  • Automatic API creation - Provide operations, get typed API
  • Simplified component registration - ctx.registerComponent() handles keys
  • Path prefix merging - Handled automatically
  • Single type parameter - Derive everything from your DefineUiPlugin type

Interfaces

PluginRuntime

Defined in: packages/ui/src/plugin.ts:385

Internal

Runtime data contributed by an instantiated plugin.

This is what gets stored in runtime.plugins[name] after a plugin factory runs. It contains the actual API client, utility functions, and configuration.

Type Parameters

Paths

Paths extends AnyPaths

OpenAPI paths type

Ops

Ops extends OperationBindings<Paths, any>

Operation bindings type

Utils

Utils extends DefineUiUtils<any> = PluginFunctionMap

Utility functions type

Options

Options extends DefineOptions<any> = EmptyRecord

Configuration options type

Routes

Routes extends DefineRoutes = DefineRoutes

Navigation routes type

Properties

api?

optional api: TypedOperations<Paths, Ops, `${string}/${string}`>

Defined in: packages/ui/src/plugin.ts:393

Typed API operations for this plugin

options?

optional options: Options

Defined in: packages/ui/src/plugin.ts:397

Configuration options passed to the plugin

routes?

optional routes: Routes

Defined in: packages/ui/src/plugin.ts:399

Navigation routes registered by this plugin

utils?

optional utils: Utils

Defined in: packages/ui/src/plugin.ts:395

Utility functions provided by this plugin


UiPluginConfig

Defined in: packages/ui/src/plugin.ts:105

Configuration for creating a UI plugin.

Type Parameters

Plugin

Plugin extends AnyUiPlugin

The plugin type (extends AnyUiPlugin)

Properties

name

name: Plugin["name"]

Defined in: packages/ui/src/plugin.ts:111

Unique plugin name. Must match the name in your DefineUiPlugin type.

Example
FILES_PLUGIN_NAME, ARROW_PLUGIN_NAME
operations?

optional operations: Plugin["ops"]

Defined in: packages/ui/src/plugin.ts:117

Operation bindings for creating typed API operations. If provided, the plugin will automatically create typed operations.

routes?

optional routes: Plugin["routes"]

Defined in: packages/ui/src/plugin.ts:123

Default navigation routes for this plugin. Can be overridden by user options.

setup()

setup: (ctx, options) => UiPluginResult<Plugin>

Defined in: packages/ui/src/plugin.ts:138

Setup function called when the plugin is installed.

Receives a context object with access to the runtime and helpers. Returns only the parts the plugin is adding - no spreading required.

Parameters
ctx

UiPluginContext<StatelySchemas<any, any>, readonly [Plugin, AnyUiPlugin]>

Plugin context with runtime access and helpers

options

User-provided options for this plugin

Plugin["options"] | undefined

Returns

UiPluginResult<Plugin>

The plugin's contributions (api, utils, routes)

utils?

optional utils: Plugin["utils"]

Defined in: packages/ui/src/plugin.ts:126

Static utility functions provided by this plugin


UiPluginContext

Defined in: packages/ui/src/plugin.ts:169

Context provided to plugin setup functions.

Gives plugins access to the runtime for reading configuration, registering components, and accessing utilities.

Type Parameters

Schema

Schema extends StatelySchemas<any, any>

The application's schema type

Augments

Augments extends readonly AnyUiPlugin[] = readonly []

Properties

client

readonly client: Client<Schema["config"]["paths"], `${string}/${string}`>

Defined in: packages/ui/src/plugin.ts:185

The openapi-fetch client for API calls

pathPrefix

readonly pathPrefix: string

Defined in: packages/ui/src/plugin.ts:188

Resolved path prefix (merged from runtime and plugin options)

registry

readonly registry: ComponentRegistry

Defined in: packages/ui/src/plugin.ts:214

Direct access to the component registry for advanced use cases.

Methods

getRuntime()

getRuntime<S, A>(): StatelyUiRuntime<S, A>

Defined in: packages/ui/src/plugin.ts:179

Get a typed view of the runtime.

Use this when you need access to runtime properties with proper typing. The type parameter should match your plugin's schema requirements.

Type Parameters
S

S extends StatelySchemas<any, any>

A

A extends readonly AnyUiPlugin[] = readonly []

Returns

StatelyUiRuntime<S, A>

registerComponent()

registerComponent(nodeType, mode, component): void

Defined in: packages/ui/src/plugin.ts:205

Register a component for a node type.

Simplified API that handles key generation automatically.

Parameters
nodeType

string

The node type name (e.g., 'RelativePath', 'Primitive')

mode

RegistryMode

'edit' or 'view'

component

ComponentType<any>

The React component to register

Returns

void

Example
ctx.registerComponent('RelativePath', 'edit', RelativePathEdit);
ctx.registerComponent('RelativePath', 'view', RelativePathView);

UiPluginResult

Defined in: packages/ui/src/plugin.ts:152

The result returned from a plugin setup function.

Plugins only return what they're adding - no need to spread runtime or plugins. The framework handles merging automatically.

Type Parameters

Plugin

Plugin extends AnyUiPlugin

The plugin type (extends AnyUiPlugin)

Properties

api?

optional api: TypedOperations<Plugin["paths"], Plugin["ops"], `${string}/${string}`>

Defined in: packages/ui/src/plugin.ts:154

Typed API operations (optional - created automatically if operations provided)

routes?

optional routes: Plugin["routes"]

Defined in: packages/ui/src/plugin.ts:156

Navigation routes for this plugin that might need access to runtime.

utils?

optional utils: Plugin["utils"]

Defined in: packages/ui/src/plugin.ts:158

Additional utility functions provided by this plugin that might need access to runtime

Type Aliases

AllUiPlugins

AllUiPlugins<Schema, Augments> = MergeUiAugments<Schema, Augments>

Defined in: packages/ui/src/plugin.ts:461

Internal

Extract the complete plugins record type from an augments array.

Alias for MergeUiAugments.

Type Parameters

Schema

Schema extends StatelySchemas<any, any>

Augments

Augments extends readonly AnyUiPlugin[]


AnyUiAugments

AnyUiAugments = readonly UiPlugin<string, any, any, any, any, any>[]

Defined in: packages/ui/src/plugin.ts:424

Array of UI plugins (used for augments type parameter).


AnyUiPlugin

AnyUiPlugin = UiPlugin<string, any, any, any, any, any>

Defined in: packages/ui/src/plugin.ts:421

Any UI plugin type (used for generic constraints).


DefineOptions

DefineOptions<T> = T

Defined in: packages/ui/src/plugin.ts:269

Define configuration options for a plugin.

Wraps your options type to ensure proper typing in the plugin system.

Type Parameters

T

T extends object = EmptyRecord

Your options object type

Example

type MyOptions = DefineOptions<{
  api?: { pathPrefix?: string };
  theme?: 'light' | 'dark';
}>;

DefineRoutes

DefineRoutes<T> = T

Defined in: packages/ui/src/plugin.ts:288

Define navigation routes for a plugin.

Routes are automatically added to the sidebar navigation.

Type Parameters

T

T extends UiNavigationOptions["routes"] = UiNavigationOptions["routes"]

Example

type MyRoutes = DefineRoutes<{
  label: 'My Plugin';
  to: '/my-plugin';
  icon: MyIcon;
  items: [
    // Any additional sub-routes
  ]
}>;

DefineUiPlugin

DefineUiPlugin<Name, Paths, Ops, Utils, Options, Routes> = UiPlugin<RequireLiteral<Name, "Plugin names must be string literals">, Paths, Ops, Utils, Options, Routes>

Defined in: packages/ui/src/plugin.ts:323

Define a UI plugin's type signature.

This is the primary helper for plugin authors. It creates a type that describes what your plugin contributes to the runtime: API operations, utilities, options, and routes.

The Name must be a string literal (not string) to ensure type-safe access via runtime.plugins[name].

Type Parameters

Name

Name extends string

Unique plugin name (must be a string literal like 'files')

Paths

Paths extends AnyPaths

OpenAPI paths type from your generated types

Ops

Ops extends OperationBindings<any, any>

Operation bindings object (use typeof MY_OPERATIONS)

Utils

Utils extends DefineUiUtils<object> = DefineUiUtils<any>

Utility functions type (optional)

Options

Options extends DefineOptions<any> = EmptyRecord

Configuration options type (optional)

Routes

Routes extends DefineRoutes = DefineRoutes

Navigation routes type (optional)

Example

// Define the plugin type
export type FilesUiPlugin = DefineUiPlugin<
  'files',                    // Plugin name - used as runtime.plugins.files
  FilesPaths,                 // OpenAPI paths
  typeof FILES_OPERATIONS,    // Operation bindings
  FilesUiUtils,               // Utility functions
  FilesOptions                // Configuration
>;

// Use in factory function signature
export function filesUiPlugin(options?: FilesOptions): UiPluginFactory<S, readonly [FilesUiPlugin]>

DefineUiUtils

DefineUiUtils<T> = T & Partial<UiUtils>

Defined in: packages/ui/src/plugin.ts:365

Define utility functions for a plugin.

Utilities are accessible via runtime.plugins[name].utils. They can optionally extend the base UiUtils interface.

Type Parameters

T

T extends object = PluginFunctionMap

Object type containing your utility functions

Example

type MyUtils = DefineUiUtils<{
  formatData: (data: RawData) => FormattedData;
  validateInput: (input: string) => boolean;
}>;

MergeUiAugments

MergeUiAugments<Schema, Augments> = Augments extends readonly [...(infer Rest extends readonly AnyUiPlugin[]), infer Last] ? MergeUiAugments<Schema, Rest> & Last extends UiPlugin<infer Name, infer Paths, infer Ops, infer Utils, infer Options, infer Routes> ? { [K in Name]: PluginRuntime<Paths, Ops, Utils, Options, Routes> } : AnyRecord : Record<string, PluginRuntime<any, any>>

Defined in: packages/ui/src/plugin.ts:434

Internal

Merge multiple plugin augments into a single type-safe record.

This recursive type extracts each plugin's name and creates a record where plugins[name] returns the correctly typed PluginRuntime.

Used by the runtime to build the plugins record type.

Type Parameters

Schema

Schema extends StatelySchemas<any, any>

Augments

Augments extends readonly AnyUiPlugin[]


PluginFunction()

PluginFunction = (...args) => unknown

Defined in: packages/ui/src/plugin.ts:342

Any function that can be a plugin utility.

Parameters

args

...any[]

Returns

unknown


PluginFunctionMap

PluginFunctionMap = Record<string, PluginFunction>

Defined in: packages/ui/src/plugin.ts:347

Map of plugin utility functions.


UiPluginFactory()

UiPluginFactory<Schema, Augments> = (runtime) => StatelyUiRuntime<Schema, Augments>

Defined in: packages/ui/src/plugin.ts:229

Factory function type for creating UI plugins.

A plugin factory receives the current runtime and returns an augmented runtime. The Augments type parameter declares what plugins are available, allowing TypeScript to provide full type inference for runtime.plugins.

Type Parameters

Schema

Schema extends StatelySchemas<any, any>

The application's schema type

Augments

Augments extends readonly AnyUiPlugin[] = readonly []

Tuple of plugin types that will be available

Parameters

runtime

StatelyUiRuntime<Schema, Augments>

Returns

StatelyUiRuntime<Schema, Augments>

See

createUiPlugin


UiPluginFactoryFn()

UiPluginFactoryFn<Plugin> = <Schema, Augments>(options?) => UiPluginFactory<Schema, Augments>

Defined in: packages/ui/src/plugin.ts:243

Configurable UI Plugin Factory Function.

Called by end users to provide initial configuration to a plugin. Returns a UiPluginFactory that passes through the augments unchanged.

Users declare all plugins in their type parameters upfront, and the factories simply operate on that declared type.

Type Parameters

Plugin

Plugin extends AnyUiPlugin

Type Parameters

Schema

Schema extends StatelySchemas<any, any>

Augments

Augments extends readonly AnyUiPlugin[]

Parameters

options?

Plugin["options"]

Returns

UiPluginFactory<Schema, Augments>

Functions

createUiPlugin()

createUiPlugin<Plugin>(config): UiPluginFactoryFn<Plugin>

Defined in: packages/ui/src/plugin.ts:521

Create a UI plugin with ergonomic API.

This helper wraps the low-level plugin pattern with:

  • No manual spreading - Return only what you're adding
  • Automatic API creation - Provide operations, get typed API
  • Simplified component registration - ctx.registerComponent() instead of makeRegistryKey
  • Path prefix merging - Handled automatically
  • Single type parameter - Derive everything from your DefineUiPlugin type

Example

Type Parameters

Plugin

Plugin extends AnyUiPlugin

The plugin type created with DefineUiPlugin

Parameters

config

UiPluginConfig<Plugin>

Plugin configuration

Returns

UiPluginFactoryFn<Plugin>

A factory function that accepts options and returns a UiPluginFactory

Example

import { createUiPlugin } from '@statelyjs/ui';

// Define the plugin type (as before)
export type FilesUiPlugin = DefineUiPlugin<
  typeof FILES_PLUGIN_NAME,
  FilesPaths,
  typeof FILES_OPERATIONS,
  FilesUiUtils,
  FilesOptions,
  typeof filesRoutes
>;

// Create the plugin factory with a single type parameter
export const filesUiPlugin = createUiPlugin<FilesUiPlugin>({
  name: FILES_PLUGIN_NAME,
  operations: FILES_OPERATIONS,
  defaultRoutes: filesRoutes,

  setup: (ctx, options) => {
    // Register components - no makeRegistryKey needed
    ctx.registerComponent('RelativePath', 'edit', RelativePathEdit);
    ctx.registerComponent('RelativePath', 'view', RelativePathView);

    // Extend other extension points
    stringModes.extend(filesStringExtension);

    // Return only what you're adding - no spreading
    return { utils: filesUiUtils };
  },
});

// Usage in user's application
const runtime = statelyUi({ ... })
  .withPlugin(filesUiPlugin({ api: { pathPrefix: '/files' } }));