Plugins
Extend workflow behavior with the plugin system.
A plugin encapsulates reusable workflow behavior. It receives the flow builder during installation and can call any builder method: addStep, configure, updateStep, addStepConditions, etc.
WorkflowPlugin Interface
interface WorkflowPlugin {
/** Unique name identifying the plugin */
name: string;
/** Semantic version (for debugging) */
version?: string;
/** Names of plugins that must be installed before this one */
dependencies?: string[];
/** Called with the flow builder instance on installation */
install(builder: flow): void;
}Using Plugins
Install with .use(). Chain multiple calls; they run in order.
const workflow = flow.create(rilay, 'checkout', 'Checkout')
.step({ id: 'personal-info', title: 'Personal Info', formConfig: personalInfoForm })
.use(loggingPlugin)
.use(analyticsPlugin)
.build();install runs immediately at the .use() call, so the plugin only sees steps added before it. To affect all steps, install after the last .step().
Creating a Plugin
This plugin wraps every step's onAfterValidation to log validated data:
import type { WorkflowPlugin } from '@rilaykit/core';
const loggingPlugin: WorkflowPlugin = {
name: 'step-logger',
version: '1.0.0',
install(builder) {
for (const step of builder.getSteps()) {
const existingCallback = step.onAfterValidation;
builder.updateStep(step.id, {
onAfterValidation: async (stepData, helper, context) => {
console.log(`[step-logger] Step "${step.id}" validated:`, stepData);
// Preserve the original callback
await existingCallback?.(stepData, helper, context);
},
});
}
},
};For configurable plugins, use a factory function:
function createAnalyticsPlugin(trackFn: (event: string, data: any) => void): WorkflowPlugin {
return {
name: 'analytics-tracker',
install(builder) {
builder.configure({
analytics: {
onWorkflowStart: (id) => trackFn('workflow_started', { workflowId: id }),
onStepComplete: (stepId, duration) => trackFn('step_completed', { stepId, duration }),
onWorkflowComplete: (id, duration, data) => trackFn('workflow_completed', { workflowId: id, duration }),
onError: (error) => trackFn('workflow_error', { message: error.message }),
},
});
},
};
}
const workflow = flow.create(rilay, 'checkout', 'Checkout')
.step(...)
.use(createAnalyticsPlugin(posthog.capture))
.build();Plugins targeting specific step IDs (updateStep, addStepConditions) throw if the step is missing — wrap in try/catch when the plugin must work across workflows with different steps.
Plugin Dependencies
dependencies lists plugin names that must already be installed. .use() validates this and throws with the missing names:
const enhancedLoggingPlugin: WorkflowPlugin = {
name: 'enhanced-logger',
dependencies: ['step-logger'],
install(builder) { /* extends the base logger */ },
};
// Works -- step-logger is installed first
flow.create(rilay, 'test', 'Test')
.use(loggingPlugin) // name: 'step-logger'
.use(enhancedLoggingPlugin)
.build();
// Throws: Plugin "enhanced-logger" requires missing dependencies: step-logger
flow.create(rilay, 'test', 'Test')
.use(enhancedLoggingPlugin)
.build();Removing Plugins
.removePlugin(name) removes a plugin from the registry — it does not roll back changes the plugin made during install. Mainly useful when cloning:
const testWorkflow = baseWorkflow
.clone('test', 'Test Workflow')
.removePlugin('production-analytics')
.use(testAnalytics);Validation
.validate() returns dependency errors (e.g. after removePlugin); .build() runs it automatically and throws on failure.
const errors = workflow.validate();
// ['Plugin "enhanced-logger" requires missing dependencies: step-logger']Best Practices
- Name plugins clearly (
analytics-posthog) so dependency errors are readable. - Install after
.step()calls when the plugin reads or updates steps. - Preserve existing callbacks when wrapping step hooks like
onAfterValidation. - Use factory functions for plugins that need configuration.