Core concepts
Persistence
Persistence is essential for delivering a seamless onboarding experience. It ensures that users can leave and return to your app without losing their progress, and enables you to store onboarding data for analytics, compliance, or personalized experiences.
OnboardJS provides flexible, pluggable persistence mechanisms in both the core engine (@onboardjs/core) and the React SDK (@onboardjs/react). You can use built-in options like localStorage, use plugins with integrations - like Supabase - or integrate with your own backend.
Why Persistence Matters
- User Experience: Users can resume onboarding after closing the browser or switching devices.
- Data Integrity: Prevents loss of progress due to accidental refreshes or navigation.
- Analytics & Personalization: Persisted context can be used for analytics or to tailor future experiences.
Persistence in @onboardjs/core
The core engine supports persistence via three configuration functions:
| Config Option | Type | Purpose |
|---|---|---|
loadData | (context) => Promise<TContext> | TContext | Load persisted context/state |
persistData | (context) => Promise<void> | void | Persist context/state |
clearPersistedData | () => Promise<void> | void | Clear persisted data |
Example: Local Storage Persistence
1const STORAGE_KEY = 'onboardjs:my-flow';
2
3const config: OnboardingEngineConfig<MyContext> = {
4 steps: [...],
5 loadData: () => {
6 const raw = localStorage.getItem(STORAGE_KEY);
7 return raw ? JSON.parse(raw) : undefined;
8 },
9 persistData: (context) => {
10 localStorage.setItem(STORAGE_KEY, JSON.stringify(context));
11 },
12 clearPersistedData: () => {
13 localStorage.removeItem(STORAGE_KEY);
14 },
15};
loadDatais called when the engine initializes.persistDatais called after each context update or navigation.clearPersistedDatais called on reset or flow completion.
Persistence in @onboardjs/react
The React SDK makes persistence even easier with the localStoragePersistence prop on OnboardingProvider. You can also provide custom handlers.
Using Local Storage (Recommended for Most Apps)
1<OnboardingProvider
2 steps={steps}
3 localStoragePersistence={{
4 key: 'onboardjs:my-flow',
5 ttl: 7 * 24 * 60 * 60 * 1000, // 7 days in ms (optional)
6 }}
7>
8 <App />
9</OnboardingProvider>
key: The localStorage key to use.ttl: Optional time-to-live in milliseconds. Expired data is cleared automatically.
Custom Persistence Handlers
You can provide your own persistence logic using these props. This is useful for integrating with custom backends and storing your onboarding state in a remote database.
customOnDataLoad?: () => Promise<TContext> | TContextcustomOnDataPersist?: (context: TContext) => Promise<void> | voidcustomOnClearPersistedData?: () => Promise<void> | void
Example: Persisting to Supabase
1<OnboardingProvider
2 steps={steps}
3 customOnDataLoad={async () => {
4 const { data } = await supabase
5 .from('onboarding')
6 .select('context')
7 .eq('user_id', user.id)
8 .single();
9 return data?.context || undefined;
10 }}
11 customOnDataPersist={async (context) => {
12 await supabase
13 .from('onboarding')
14 .upsert({ user_id: user.id, context });
15 }}
16 customOnClearPersistedData={async () => {
17 await supabase
18 .from('onboarding')
19 .delete()
20 .eq('user_id', user.id);
21 }}
22>
23 <App />
24</OnboardingProvider>
Best Practices
- Keep context serializable: Avoid non-serializable values (functions, class instances) in your context.
- Version your data: If your onboarding flow changes, use a version field to handle migrations.
- Handle partial/incomplete flows: Check for incomplete context and guide users accordingly.
- Secure sensitive data: Don’t store secrets or sensitive information in localStorage.
- Clear data on completion: Use
clearPersistedDataor equivalent to remove onboarding data when the flow is finished.
Troubleshooting
- Corrupted State: If
loadDatareturns invalid or corrupted data, reset the onboarding flow and clear persisted data. - Persistence Not Working: Ensure your
persistData/customOnDataPersistfunctions are being called (check for errors in the console). - Multiple Flows: Use unique storage keys for different onboarding flows or user segments.
Example: Full Persistence Flow
1const STORAGE_KEY = 'onboardjs:my-flow';
2
3const config: OnboardingEngineConfig<MyContext> = {
4 steps: [...],
5 loadData: () => {
6 const raw = localStorage.getItem(STORAGE_KEY);
7 return raw ? JSON.parse(raw) : undefined;
8 },
9 persistData: (context) => {
10 localStorage.setItem(STORAGE_KEY, JSON.stringify(context));
11 },
12 clearPersistedData: () => {
13 localStorage.removeItem(STORAGE_KEY);
14 },
15 onFlowComplete: (context) => {
16 // Optionally clear data or archive context
17 localStorage.removeItem(STORAGE_KEY);
18 },
19};
Summary
- OnboardJS supports flexible persistence via config functions and React provider props.
- Use localStorage for simple cases, or integrate with your backend for advanced needs.
- Always keep context serializable and secure.
- Clear persisted data when onboarding is complete or reset.
For more, see The Onboarding Context and OnboardingProvider.

