Dynamic Multi-Step Form for Webflow
A dynamic, multi-step form component for Webflow, powered by Airtable for easy configuration and management.
Installation
npm install
Usage
To start the development server, run the following command:
npm run dev
This will start the Astro development server, and you can view the component at http://localhost:4321.
Project structure
src/components/DynamicFormGenerator.jsx: The core React component that handles form logic, state management, validation, and rendering.src/components/DynamicFormGenerator.webflow.tsx: The Webflow Code Component definition file. It defines the props that appear in the Webflow Designer's settings panel.src/styles/component-styles.css: The CSS file containing styles for the component, written using Tailwind CSS and DaisyUI.
How it works
This component connects Airtable for configuration, Webflow for presentation, and a webhook for data processing.
- Form Configuration in Airtable: Design your form in an Airtable Base. Define steps, fields, validation, and conditional logic.
- JSON Generation: An Airtable script generates a JSON configuration for your form.
- Webflow Component: Add the "Dynamic Form Generator" component in Webflow and paste the JSON configuration.
- Form Rendering: The React component parses the JSON and renders the multi-step form.
- Data Submission: Form data is sent to a specified webhook.
- Airtable Automation: The webhook triggers an Airtable Automation to process the data.
Setup guide
1. Configure the form in Airtable
- Open the Airtable Base Template and duplicate it.
- In the Forms table, create a record for your form.
- In the Form Structure table, add your questions, assign them to steps, and link them to your form.
- Copy the JSON from the
Form Config Scriptfield in your Forms table record.
2. Set up the Airtable automation
In your Airtable base, go to Automations.
Create an automation with a "When webhook received" trigger.
Copy the webhook URL.
Add actions to process the form data
Action Setup Details
Action 1: Create record: Create a new record in a
Submissionstable with the data received from the webhook.Action 2: Run a script: Add custom logic, to create a new form-specific table for submissions.
Script Configuration
Inputs
Name Value formDatapayloadformIdformIdScript Code
// If Form Table doesn't exist - create it const { formData, formId } = input.config(); const baseId = base.id; // Get Table const tables = base.tables.map((table) => table.name); const table = tables.find((name) => name == formId); // Create the table with fields if (!table) { // Get fields from form structure table const structureTable = base.getTable("Form Structure"); const query = await structureTable.selectRecordsAsync({ fields: structureTable.fields, }); const structureRecords = query.records; const formStructure = structureRecords.filter((r) => r .getCellValue("FormID") .map((f) => f.name) .includes(formId) ); console.log("formStructure", formStructure); // For each record, create a field in the new table const fields = formStructure.map((record) => { // Determine field type const getFieldType = (fieldType) => { switch (fieldType.toLowerCase()) { case "number": return "number"; case "text area": return "richText"; case "email": return "email"; case "dropdown": return "singleSelect"; case "scale": return "number"; default: return "singleLineText"; } }; const obj = { name: record.name, description: record.getCellValueAsString("User Label"), type: getFieldType(record.getCellValueAsString("Field Type")), }; if (obj.type == "number") { obj.options = {}; obj.options.precision = 0; } if (obj.type === "singleSelect") { obj.options = {}; obj.options.choices = record .getCellValue("Options") .map((values) => ({ name: values.name })); } return obj; }); console.log(fields); const url = `https://api.airtable.com/v0/meta/bases/${baseId}/tables`; await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${input.secret("airtableKey")}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: formId, fields: fields, }), }) .then((response) => response.json()) .then((data) => console.log(data)); }- Action 3: Run a Script: Add a script to create new records in the form-specfic table
Script Configuration
Inputs
Name Value formDatapayloadformIdformIdScript Code
const { data, formId } = input.config(); const jsonData = JSON.parse(data); console.log(jsonData); const table = base.getTable(formId); const fields = table.fields; const singleSelectFields = fields.filter( (field) => field.type == "singleSelect" ); const numberFields = fields.filter((field) => field.type === "number"); for (let entry in jsonData) { // Convert to single select format if (singleSelectFields.map((field) => field.name).includes(entry)) jsonData[entry] = { name: jsonData[entry] }; // Convert to number format if (numberFields.map((fields) => fields.name).includes(entry)) jsonData[entry] = parseInt(jsonData[entry]); } table.createRecordAsync(jsonData);
3. Add the component in Webflow
- Share the component to your Webflow workspace using the Webflow CLI:
npx webflow library share - Install the library in your Webflow site and add the component to a page.
- Configure the component's props in the Settings panel.
Component properties
| Prop Name | Type | Description |
|---|---|---|
formName |
Text |
The title displayed at the top of the form. |
formConfig |
Text |
The JSON string that defines the form structure, copied from the Airtable base. |
webhookUrl |
Text |
The URL that the form data will be submitted to. |
formID |
Text |
A unique identifier for the form, which gets included in the submission payload. |
devMode |
Variant |
If true, submitting the form shows a preview of the JSON payload instead of sending it to the webhook. Useful for debugging. |
Community
For help, discussion about examples and best practices:
Join the Webflow App Developers Forum
To connect and swap ideas with other Webflow developers: