Build a dynamic form system where form fields are defined in Firestore with label, type, and validation rules. Render forms using Conditional Builder for each field type: text inputs, dropdowns, date pickers, and checkboxes. Collect all values in a Page State JSON Map keyed by field ID. Validate required fields on submit and save responses to a form_responses collection.
Building a Dynamic Form System in FlutterFlow
Static forms require modifying the app for every change. A dynamic form builder reads field definitions from Firestore and renders the form at runtime. This lets admins create forms, add fields, and change validation without touching the app code.
Prerequisites
- A FlutterFlow project with Firestore configured
- A forms collection with a fields subcollection in Firestore
- Understanding of Conditional Builder and Page State in FlutterFlow
Step-by-step guide
Define the form schema in Firestore with typed field documents
Define the form schema in Firestore with typed field documents
Create a forms collection with fields: title (String), description (String). Under each form, create a fields subcollection with documents containing: label (String), fieldType (String: text/number/email/select/multiselect/date/file/checkbox), options (String Array for select/multiselect), isRequired (Boolean), order (int), placeholder (String). Add 4-6 field documents with different types for testing. The order field determines display sequence.
Expected result: Firestore contains a form definition with typed field documents in the correct order.
Load form fields and render dynamically with Conditional Builder
Load form fields and render dynamically with Conditional Builder
Create a FormPage with Route Parameter formId. Add a Backend Query on the fields subcollection ordered by the order field. Add a Column with Generate Dynamic Children bound to the query results. Inside each child, use Conditional Builder checking the fieldType value: 'text' → TextField, 'number' → TextField (keyboardType: number), 'email' → TextField (keyboardType: email), 'select' → DropDown (options from field.options), 'date' → DateTimePicker, 'checkbox' → Checkbox, 'file' → FlutterFlowUploadButton. Each input's label comes from the field.label parameter.
Expected result: The form renders different input widgets based on the field type defined in Firestore.
Collect form values in a Page State Map on each input change
Collect form values in a Page State Map on each input change
Add a Page State variable formData (JSON type, default empty map). On each input widget's On Changed event, update the formData Map: set key as the field document ID, value as the entered data. For text fields, the value is the text string. For select, the selected option. For date, the selected date. For checkbox, true/false. For file upload, the download URL. This accumulates all form responses in one structured object.
Expected result: Every input change updates the formData Map with the field ID as key and answer as value.
Validate required fields and submit the form response
Validate required fields and submit the form response
On the Submit button tap, iterate through the field documents. For each field where isRequired is true, check if formData contains a non-empty value for that field ID. If any required field is missing, show a Snackbar error specifying which field needs attention. If all required fields are filled, create a document in form_responses: formId, userId (current user or anonymous), formData Map, submittedAt timestamp. Show a success Snackbar or navigate to a thank-you page.
Expected result: Form validates all required fields before submission and saves the complete response to Firestore.
Add inline validation indicators per field
Add inline validation indicators per field
For each field in the dynamic children, add a Text widget below the input with Conditional Visibility: show when the field isRequired AND formData[fieldId] is empty AND a Page State submitAttempted is true. Color the Text red and show the message 'This field is required.' Set submitAttempted to true on the first Submit tap. This shows which specific fields need attention, not just a generic error.
Expected result: Red validation messages appear below incomplete required fields after the first submit attempt.
Complete working example
1FIRESTORE DATA MODEL:2 forms/{formId}3 title: String4 description: String5 └── fields/{fieldId}6 label: String7 fieldType: "text" | "number" | "email" | "select" | "multiselect" | "date" | "file" | "checkbox"8 options: ["Option A", "Option B"] (select/multiselect only)9 isRequired: Boolean10 order: int11 placeholder: String1213PAGE STATE:14 formData: JSON Map = {}15 submitAttempted: Boolean = false1617WIDGET TREE:18 Column (padding: 16)19 ├── Text (form.title, Headline Small)20 ├── Text (form.description, Body Medium, grey)21 ├── SizedBox (24)22 ├── Column (Generate Dynamic Children → fields ordered by order)23 │ └── Per field:24 │ Column25 │ ├── Text (field.label + " *" if required, Body Medium, bold)26 │ ├── SizedBox (8)27 │ ├── Conditional Builder (field.fieldType)28 │ │ text → TextField (placeholder)29 │ │ number → TextField (keyboardType: number)30 │ │ email → TextField (keyboardType: email)31 │ │ select → DropDown (field.options)32 │ │ date → DateTimePicker33 │ │ checkbox → Checkbox34 │ │ file → FlutterFlowUploadButton35 │ ├── Text ("Required", red, Conditional: required && empty && submitAttempted)36 │ └── SizedBox (16)37 └── Button "Submit" (Primary, full width)38 On Tap:39 1. Set submitAttempted = true40 2. Validate required fields in formData41 3. If valid → Create Document form_responses42 4. If invalid → Show Snackbar errorCommon mistakes when creating a Custom Form Builder in FlutterFlow
Why it's a problem: Not validating required fields before submission
How to avoid: Check each field's isRequired flag against the formData Map before creating the response document. Show inline error messages per field.
Why it's a problem: Using a flat List instead of a Map for form data collection
How to avoid: Use a JSON Map with field document IDs as keys for robust response collection.
Why it's a problem: Hardcoding form fields in the widget tree instead of using dynamic rendering
How to avoid: Store form schemas in Firestore and render dynamically. New forms and fields are created by adding Firestore documents.
Best practices
- Store form schemas in Firestore for admin-editable forms without app changes
- Use Conditional Builder to render the correct input widget per field type
- Collect responses in a JSON Map keyed by field document ID for reliability
- Validate required fields with inline error messages before submission
- Show an asterisk (*) next to required field labels for visual indication
- Add placeholder text to guide users on expected input format
- Save submission timestamp and userId for response tracking and analytics
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to build a dynamic form builder in FlutterFlow where form fields are defined in Firestore. Each field has a type (text, select, date, checkbox, file) and renders the appropriate widget. Collect values in a Map and validate required fields on submit.
Create a page with a heading, a description, a dynamic list of form fields (I will configure the types), and a submit button at the bottom.
Frequently asked questions
Can non-technical team members create new forms?
Yes. They add documents to the Firestore forms and fields collections via the Firebase Console or a custom admin page. No FlutterFlow editing needed.
How do I add conditional field visibility (show field B if field A = X)?
Add a dependsOn field to the Firestore schema (dependsOnFieldId, dependsOnValue). In the rendering logic, check formData[dependsOnFieldId] == dependsOnValue with Conditional Visibility.
Can I pre-fill form fields with existing data?
Yes. Pass an existing response Map as a Route Parameter. On page load, set formData to the passed values. Input widgets read their initial values from formData[fieldId].
How do I export form responses to a spreadsheet?
Create a Cloud Function that queries form_responses, flattens the formData Maps, and generates a CSV file. Download the CSV from a button in an admin dashboard.
Can I support file upload fields?
Yes. Use FlutterFlowUploadButton for the 'file' field type. The upload returns a download URL which is stored in formData[fieldId] as a string.
Can RapidDev help build a form management system?
Yes. RapidDev can build form builders with conditional logic, multi-step forms, admin dashboards, response analytics, and export features.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation