rilaykit
Workflow

Analytics

Track workflow events, step timing, and user behavior with callback-based analytics.

Workflows fire optional callbacks at lifecycle moments — start, step transitions, skips, errors, completion. Wire them to any provider (Segment, PostHog, your own backend).

WorkflowAnalytics Interface

interface WorkflowAnalytics {
  onWorkflowStart?(workflowId: string, context: WorkflowContext): void;
  onWorkflowComplete?(workflowId: string, duration: number, data: any): void;
  onWorkflowAbandon?(workflowId: string, currentStep: string, data: any): void;
  onStepStart?(stepId: string, timestamp: number, context: WorkflowContext): void;
  onStepComplete?(stepId: string, duration: number, data: any, context: WorkflowContext): void;
  onStepSkip?(stepId: string, reason: string, context: WorkflowContext): void;
  onError?(error: Error, context: WorkflowContext): void;
}
ParameterDescription
workflowIdThe id passed to flow.create.
contextThe WorkflowContext at event time (current step, data, visited steps).
durationMilliseconds: time spent on the step (onStepComplete) or since workflow start (onWorkflowComplete).
dataThe completed step's slice (onStepComplete) or the projected workflow data (onWorkflowComplete), both in the authored shape.
reasonWhy the step was skipped — currently always 'user_skip'.

Configuration

Pass an analytics object to .configure():

const workflow = flow.create(rilay, 'onboarding', 'User Onboarding')
  .step({ id: 'personal-info', title: 'Personal Info', formConfig: personalInfoForm })
  .step({ id: 'preferences', title: 'Preferences', formConfig: preferencesForm, allowSkip: true })
  .configure({
    analytics: {
      onWorkflowStart: (id, context) => {
        track('workflow_started', { id, totalSteps: context.totalSteps });
      },
      onStepComplete: (stepId, duration, stepData) => {
        track('step_complete', { stepId, duration, fields: Object.keys(stepData).length });
      },
      onStepSkip: (stepId, reason) => track('step_skipped', { stepId, reason }),
      onWorkflowComplete: (id, duration, data) => {
        track('workflow_complete', { id, seconds: Math.round(duration / 1000) });
      },
      onError: (error, context) => {
        captureException(error, { workflowId: context.workflowId });
      },
    },
  });

Replace track / captureException with your provider's calls (analytics.track, posthog.capture, a fetch to your backend, …).

Event Timing

EventWhen it fires
onWorkflowStartOnce, when the workflow reaches its first interactive step (after any async persistence load settles).
onStepStartEach time the active step changes, including the first step.
onStepCompleteOn forward navigation away from a step. Not fired on backward navigation, for skipped steps, or for the last step — completion is carried by onWorkflowComplete.
onStepSkipWhen skipStep() is called.
onWorkflowCompleteAfter the last step is submitted. data is the same projection onComplete receives — only answered visible steps, no placeholders.
onWorkflowAbandonOn unmount, if the workflow started but never completed.
onErrorOn any workflow error path (below).

Error paths

Every workflow error routes through onError — and the global monitor, when initialized: step-transition failures, onAfterValidation throws, submission throws, and persistence save/load/remove failures (as WorkflowPersistenceError). A validation error that blocks Next is not an error path and fires no onError.

Global Monitoring

If a global monitor is initialized (initializeMonitoring from @rilaykit/core), all analytics events are also forwarded to it as workflow_navigation events carrying WorkflowPerformanceMetrics:

interface WorkflowPerformanceMetrics {
  timestamp: number;
  duration: number;
  workflowId: string;
  stepCount: number;
  currentStepIndex: number;
  navigationDuration: number;
  persistenceDuration?: number;
  conditionEvaluationDuration: number;
}

Step skips, slow navigation (> 1 s), and slow condition evaluation (> 100 ms) are flagged medium priority; errors go through monitor.trackError.

Monitor integration is opt-in. Without a global monitor, analytics callbacks work standalone with no overhead.

useWorkflowAnalytics Hook

WorkflowProvider drives all of the above through the internal useWorkflowAnalytics hook — start times, step durations, and event dispatch are automatic; you never call it yourself. It returns manual escape hatches (trackStepSkip, trackError, trackNavigation, trackConditionEvaluation) used by the navigation and submission hooks.

On this page