Building Workflows
How to define multi-step workflows with the flow builder.
A workflow is a sequence of steps — each usually a RilayKit form — chained together with navigation logic.
The flow Builder
rilay.flow(id?, name?, description?) starts a builder. ID and name are auto-generated when omitted. Chain .step() per step, .configure() for workflow-level options, then pass the builder to <Workflow />.
import { rilay } from '@/lib/rilay';
const workflow = rilay
.flow('user-onboarding', 'User Onboarding')
.step({ id: 'personal-info', title: 'Personal Information', formConfig: personalInfoForm })
.step({ id: 'preferences', title: 'Preferences', formConfig: preferencesForm, allowSkip: true });formConfig accepts a form builder instance directly — it is built automatically.
When to use .build()
<Workflow /> builds the workflow for you. Call .build() manually only when you need the final serializable WorkflowConfig object — e.g. to save it as JSON or for debugging.
Step Configuration
Options for .step():
| Option | Type | Description |
|---|---|---|
id | string | Unique identifier. Auto-generated if omitted. |
title | string (required) | Shown in the stepper. |
description | string | Short step description. |
formConfig | FormConfiguration | form builder (required) | The step's form. |
renderer | CustomStepRenderer | Custom renderer for this step's body. |
allowSkip | boolean | ({ allData }) => boolean | Enables <FlowSkip> (from rilaykit/react). Default false. |
metadata | StepMetadata | icon, category, tags, and custom fields — readable via hooks and context. |
conditions | StepConditionalBehavior | Conditional visibility and skippable state. |
after | (step: StepContext) => void | Promise<void> | Runs after successful validation, before navigation. See Advanced Workflows. |
onAfterValidation | deprecated | Legacy 3-parameter callback. Use after. |
.configure() accepts analytics (WorkflowAnalytics callbacks) and persistence ({ adapter, options?, userId? }).
Complete Example: User Onboarding Workflow
import { custom, email, minLength, required } from 'rilaykit';
import { rilay } from '@/lib/rilay';
// 1. Define the forms
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',
props: { label: 'I agree to the terms and conditions' },
validation: { validate: [custom(value => value === true, 'You must accept the terms')] },
});
// 2. Chain them into a 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) => {
// Pre-fill the next visible step with the user's email
step.next.prefill({ email: step.data.email });
},
})
.step({ id: 'personal-info', title: 'Your Profile', formConfig: profileForm })
.step({ id: 'confirmation', title: 'Confirmation', formConfig: confirmationForm })
.configure({
analytics: {
onWorkflowStart: (id) => console.log(`Started: ${id}`),
onWorkflowComplete: (id, duration, data) => console.log(`Completed: ${id} in ${duration}ms`, data),
},
});Pass onboardingWorkflow to <Workflow />. See Field and Form Validation for the validators used above.