Custom Steps (Host Application Integration)
The @dfbe/core form engine allows host applications to inject arbitrary React components as Custom Steps. This is useful when you need to render content that goes beyond the standard JSON schema definitions (e.g., a complex payment gateway, an interactive map, or a custom verification screen).
1. Adding a Custom Step via the Builder
You can add a "Custom Step" marker directly through the Studio Form Builder UI.
When you add a Custom Step, the builder will generate a step configuration with type: "custom" in the schema.steps array.
This marker reserves a spot in the form flow. The builder will not allow you to add sections or fields to a custom step, as the content is expected to be provided by the host application.
{ "id": "step_2-custom", "title": "Payment Verification", "type": "custom" }
2. Registering the React Component
Once the custom step marker is in the schema, the host application must provide the actual React component to render when that step is active. You do this by passing a customSteps object to the FormEngineProvider.
import { FormEngineProvider } from "@dfbe/core"; import PaymentVerificationComponent from "./PaymentVerificationComponent"; // Map the step ID from the schema to your React component const myStepRegistry = { "step_2-custom": PaymentVerificationComponent, }; export default function App() { return <FormEngineProvider schema={schema} customSteps={myStepRegistry} />; }
3. Navigation and Validation Guards
Custom components do not receive next() or previous() as props. Instead, the form engine continues to manage the navigation buttons at the bottom of the form.
If your custom component needs to perform asynchronous operations (like API calls or payments) before the user is allowed to proceed to the next step, you must register a Step Guard using the useFormEngine hook.
The setStepGuard function accepts a callback that must return true to allow navigation, or false to block it.
import { useEffect } from "react"; import { useFormEngine } from "@dfbe/core"; export default function PaymentVerificationComponent({ step }) { const { setStepGuard } = useFormEngine(); useEffect(() => { // 1. Register the guard when the component mounts setStepGuard(async () => { // Perform your custom async logic or validation here const isPaymentSuccessful = await checkPaymentStatus(); if (!isPaymentSuccessful) { alert("Please complete the payment first!"); return false; // Blocks navigation } return true; // Allows navigation to the next step }); // 2. Clear the guard when the component unmounts return () => setStepGuard(null); }, [setStepGuard]); return ( <div className="p-4 border rounded"> <h2>Complete your payment for {step.title}</h2> {/* Your custom UI goes here */} </div> ); }
Key Differences from Schema Steps
- Custom Steps bypass the built-in Zod schema validation since they have no
fields. Data collection inside a Custom Step must be handled independently by the host application. - The step guard executes exactly when the user clicks the engine's "Next" button, keeping the UX consistent across both schema steps and custom steps.