Flowboard returns collected user input as formData.
Each key in formData matches the id of an input component in your flow. If your flow has inputs with IDs like email, plan, and accept_terms, Flowboard returns data in this shape:
{
"email" : "ada@example.com" ,
"plan" : "pro" ,
"accept_terms" : true
}
You can read that data in three places:
API When to use it What you receive onOnboardEnd(formData)When the flow is completed The final collected data onStepChange(pageId, pageStep, formData)When you want progress-aware analytics or autosave The current data snapshot at that step ctx.formDataInside a custom screen or custom action The latest data collected before that screen or action
Simple example
Start with onOnboardEnd when you only need the final values.
import { Flowboard } from 'flowboard-react' ;
export async function startFlow () {
await Flowboard . launchOnboarding ({
onOnboardEnd : ( formData ) => {
const email =
typeof formData . email === 'string' ? formData . email . trim () : '' ;
const selectedPlan =
typeof formData . plan === 'string' ? formData . plan : 'free' ;
console . log ( 'Flow completed' , {
email ,
selectedPlan ,
});
},
});
}
Keep your Flowboard input IDs stable. Those IDs become your formData keys, so changing them changes the data contract in your app.
Use data before the flow ends
You do not have to wait until completion.
Use onStepChange for analytics, progress-aware UI, or autosave, and use ctx.formData inside custom screens when a screen needs values from earlier steps.
Expo / React Native
Flutter
await Flowboard . launchOnboarding ({
onStepChange : ( pageId , pageStep , formData ) => {
analytics . track ( 'flow_step_viewed' , {
pageId ,
pageStep ,
selectedPlan:
typeof formData . plan === 'string' ? formData . plan : undefined ,
});
},
customScreenBuilder : ( ctx ) => {
if ( ctx . screenData . id !== 'profile_summary' ) {
return null ;
}
return (
< ProfileSummaryCard
name = {typeof ctx . formData . fullname === 'string' ? ctx . formData . fullname : '' }
email = {typeof ctx . formData . email === 'string' ? ctx . formData . email : '' }
onContinue = { ctx . onNext }
/>
);
},
});
Recommended production setup
In production, do not pass raw formData directly to the rest of your app.
Map it into a small, typed payload first. This gives you one place to validate keys, apply defaults, and keep your backend contract stable even if the flow grows.
// src/flowboard/flowResult.ts
type FlowboardSignupPayload = {
email : string ;
fullName : string ;
plan : 'free' | 'pro' | 'team' ;
acceptedTerms : boolean ;
};
function readString (
formData : Record < string , unknown >,
key : string
) : string {
const value = formData [ key ];
return typeof value === 'string' ? value . trim () : '' ;
}
function readPlan (
formData : Record < string , unknown >
) : FlowboardSignupPayload [ 'plan' ] {
const value = formData . plan ;
return value === 'pro' || value === 'team' ? value : 'free' ;
}
export function buildFlowboardSignupPayload (
formData : Record < string , unknown >
) : FlowboardSignupPayload {
return {
email: readString ( formData , 'email' ),
fullName: readString ( formData , 'fullname' ),
plan: readPlan ( formData ),
acceptedTerms: Boolean ( formData . accept_terms ),
};
}
// src/features/onboarding/launchSignupFlow.ts
import { Flowboard } from 'flowboard-react' ;
import { buildFlowboardSignupPayload } from '../../flowboard/flowResult' ;
async function submitOnboardingResult ( payload : FlowboardSignupPayload ) {
await api . post ( '/onboarding/complete' , payload );
}
export async function launchSignupFlow () {
await Flowboard . launchOnboarding ({
onStepChange : ( pageId , pageStep , formData ) => {
analytics . track ( 'flow_step_viewed' , {
pageId ,
pageStep ,
plan: buildFlowboardSignupPayload ( formData ). plan ,
});
},
onOnboardEnd : ( formData ) => {
const payload = buildFlowboardSignupPayload ( formData );
void submitOnboardingResult ( payload );
},
});
}
Replace analytics, api, and any helper components in these examples with the services and UI primitives used in your app.
Summary
Use this rule:
Read final data in onOnboardEnd
Read progress snapshots in onStepChange
Read live values in ctx.formData
Normalize the payload before sending it to your backend