rilaykit
Workflow

Building Workflows

How to define multi-step workflows with the flow builder.

A workflow is a sequence of steps, where each step is usually a Rilaykit form. The workflow builder allows you to chain these steps together and configure the navigation logic between them.

The flow Builder

The flow class provides a static factory method to build multi-step workflows. It follows the same builder pattern as forms, with method chaining and full type safety.

1. Create your Form Configurations

First, you need a formConfig for each step of your workflow. You'll create these using the standard form builder.

import { rilay } from '@/lib/rilay';

const personalInfoForm = rilay.form('personal-info').add(...).build();
const preferencesForm = rilay.form('preferences').add(...).build();
const reviewForm = rilay.form('review').add(...).build();

You can also pass a form builder instance directly to a step. It will be built automatically.

2. Start the Flow Builder

Use .flow() on your rilay instance to begin building your workflow. The workflow ID and name are optional — if not provided, they will be auto-generated.

import { rilay } from '@/lib/rilay';

// With explicit ID and name (recommended for production)
const workflowBuilder = rilay.flow(
    'user-onboarding',
    'User Onboarding Workflow',
    'A multi-step workflow to onboard new users' // Optional description
  )

// With auto-generated ID and default name (useful for prototyping)
const quickWorkflow = rilay.flow()

3. Add Steps

Use .step() to add steps to your workflow. You must provide at least a title and formConfig.

//...
.step({
  id: 'personal-info',
  title: 'Personal Information',
  description: 'Tell us about yourself',
  formConfig: personalInfoForm,
})
.step({
  id: 'preferences',
  title: 'Preferences',
  description: 'Set your preferences',
  formConfig: preferencesForm,
  allowSkip: true, // This step is optional
})
.step({
  id: 'review',
  title: 'Review & Feedback',
  description: 'Review your information',
  formConfig: reviewForm,
})
// ...

4. Configure Navigation and Hooks

You can configure the overall workflow behavior with the unified .configure() method.

// ...
.configure({
  analytics: {
    onWorkflowStart: (workflowId) => console.log(`Workflow ${workflowId} started.`),
    onStepComplete: (stepId, duration) => console.log(`Step ${stepId} took ${duration}ms.`),
  }
})
// ...

5. Build the Configuration

Finally, call .build() to get the final workflowConfig object.

const workflowConfig = rilay.flow(...)
  .step(...)
  .step(...)
  .configure(...)
  .build();

This configuration can now be passed to the <Workflow> component, which will build it automatically if you pass the builder instance.

When to use .build()

Just like with forms, you often don't need to call .build() on a workflow builder. The <Workflow /> component handles it for you.

You should call .build() manually when you need the final, serializable WorkflowConfig object, for example, to save it as JSON or for debugging.

Step Configuration

The object passed to .step() has several options:

  • id (optional): A unique identifier for the step. Auto-generated if not provided.
  • title (required): A human-readable title for the step, often shown in the stepper.
  • description (optional): A short description of the step.
  • formConfig (required): The form configuration or form builder instance for this step.
  • renderer (optional): A custom renderer for the body of this specific step.
  • allowSkip (optional, default: false): If true, users can skip this step via <WorkflowSkipButton>.
  • metadata (optional): Structured metadata object with icon, category, tags, and custom fields.
  • conditions (optional): Conditional behavior configuration (visibility, skippable state).
  • after (recommended): Simplified callback with single parameter containing step data and helpers. See Advanced Workflows.
  • onAfterValidation (deprecated): Legacy 3-parameter callback. Use after instead for better DX.

Complete Example: User Onboarding Workflow

Let's put it all together. Here is a full example of a 3-step user onboarding workflow.

First, we define the forms for each step:

// 1. Define the forms for each step
const accountForm = rilay.form('account-form')
  .add(
    { id: 'email', type: 'email', props: { label: 'Email' }, validation: { validate: [required(), email()] } },
    { id: 'password', type: 'password', props: { label: 'Password' }, validation: { validate: [required(), minLength(8)] } }
  );

const profileForm = rilay.form('profile-form')
  .add(
    { id: 'firstName', type: 'text', props: { label: 'First Name' } },
    { id: 'lastName', type: 'text', props: { label: 'Last Name' } }
  );

const confirmationForm = rilay.form('confirmation-form')
  .add({
    id: 'terms',
    type: 'checkbox', // Assumes a 'checkbox' component is registered
    props: {
      label: 'I agree to the terms and conditions',
    },
    validation: {
      validate: [
        custom(value => value === true, 'You must accept the terms')
      ]
    }
  });

About Validation

This example uses several built-in validators. To learn more about how validation works in Rilay, check out our in-depth Field and Form Validation guide.

Now, we use the flow builder to chain them together into a workflow:

// 2. Build the workflow
export const onboardingWorkflow = rilay.flow(
    'onboarding',
    'New User Onboarding'
  )
  .step({
    id: 'account-creation',
    title: 'Create Account',
    formConfig: accountForm,
    metadata: { icon: 'user', category: 'account' },
    after: async (step) => {
      // Simplified callback signature
      console.log('Account created:', step.data.email);

      // Pre-fill next step with user's email
      step.next.prefill({
        email: step.data.email
      });
    }
  })
  .step({
    id: 'personal-info',
    title: 'Your Profile',
    formConfig: profileForm,
    metadata: { icon: 'profile', category: 'profile' }
  })
  .step({
    id: 'confirmation',
    title: 'Confirmation',
    formConfig: confirmationForm,
    metadata: { icon: 'check', category: 'confirmation' }
  })
  .configure({
    analytics: {
      onWorkflowStart: (id) => console.log(`Started: ${id}`),
      onWorkflowComplete: (id, totalTime) => console.log(`Completed: ${id} in ${totalTime}ms`),
    },
  });

New in v0.2: The simplified after callback replaces the verbose onAfterValidation (still supported but deprecated).

This onboardingWorkflow builder instance is ready to be passed to the <Workflow /> component, providing a complete multi-step form experience out of the box.

On this page