rilaykit
Workflow

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.

On this page