Build a questionnaire system that reads question definitions from Firestore, including input type, validation rules, and options. Render each question dynamically using Generate Dynamic Children with a Conditional Builder switching between TextField, DateTimePicker, RadioButton, and CheckboxGroup based on the inputType field. Collect all answers in a Page State Map, validate required fields and regex patterns on submit, and save responses to a questionnaire_responses collection.
Building a Dynamic Questionnaire System in FlutterFlow
Hardcoded questionnaires require an app update for every change. A dynamic system reads questions from Firestore at runtime, rendering the correct input widget for each question type. Admins can add, remove, or modify questions without touching the app. This tutorial covers schema design, dynamic rendering, validation, and response storage.
Prerequisites
- A FlutterFlow project with Firestore configured
- A questionnaires collection with a questions subcollection in Firestore
- Understanding of Conditional Builder and Page State in FlutterFlow
- Basic knowledge of regular expressions for input validation
Step-by-step guide
Define the questionnaire schema in Firestore
Define the questionnaire schema in Firestore
Create a questionnaires collection with fields: title (String), description (String), isActive (Boolean). Under each questionnaire, create a questions subcollection. Each question document contains: questionText (String), inputType (String: text, number, email, phone, date, singleSelect, multiSelect), options (String Array for select types), isRequired (Boolean), placeholder (String), validationRegex (String, optional), order (int). Add five to six test questions with varied input types and at least two with validation regex.
Expected result: Firestore contains a questionnaire with typed question documents including validation patterns.
Load questions and render dynamically with Conditional Builder
Load questions and render dynamically with Conditional Builder
Create a QuestionnairePage with a Route Parameter questionnaireId. Add a Backend Query on the questions subcollection ordered by the order field. Add a Column with Generate Dynamic Children bound to the query results. Inside each dynamic child, use a Conditional Builder checking inputType: text and number map to TextField (number gets keyboardType number), email maps to TextField with email keyboard, phone to TextField with phone keyboard, date to DateTimePicker, singleSelect to RadioButton group populated from the options array, multiSelect to a CheckboxGroup with the options. Place the questionText as a label Text above each input.
Expected result: The questionnaire renders the correct input widget for each question based on its Firestore inputType.
Collect answers in a Page State Map on input change
Collect answers in a Page State Map on input change
Add a Page State variable answers (JSON type, default empty map) and a variable submitAttempted (Boolean, default false). On each input widget's On Changed callback, update the answers Map: set the key as the question document ID and the value as the entered data. For singleSelect, store the selected option string. For multiSelect, store the selected options as a list. For date, store the selected DateTime. This accumulates all responses in one structured Map for validation and submission.
Expected result: Every answer change updates the answers Map with the question ID as key and the user input as value.
Validate required fields and regex patterns before submission
Validate required fields and regex patterns before submission
On the Submit button tap, first set submitAttempted to true. Loop through the question documents. For each question where isRequired is true, check that answers[questionId] exists and is not empty. For questions with a validationRegex, check that the answer matches the pattern using a Custom Function that calls RegExp(regex).hasMatch(value). If any validation fails, show a SnackBar listing the first failing question. Display inline red error text below each invalid field using Conditional Visibility: show when submitAttempted is true AND the field fails validation.
1// Custom Function: validateRegex2bool validateRegex(String value, String regex) {3 if (regex.isEmpty) return true;4 return RegExp(regex).hasMatch(value);5}Expected result: Invalid or missing answers show red inline errors and the form does not submit until all validations pass.
Save the response and show completion feedback
Save the response and show completion feedback
After all validations pass, create a document in questionnaire_responses with fields: questionnaireId, userId (current user or anonymous identifier), answers (the complete Map), submittedAt (server timestamp). Show a success SnackBar or navigate to a thank-you page. Add a LinearPercentIndicator at the top of the page bound to the number of answered questions divided by total questions to show progress as the user fills in responses.
Expected result: Valid responses are saved to Firestore with a progress bar showing completion rate during filling.
Complete working example
1FIRESTORE DATA MODEL:2 questionnaires/{questionnaireId}3 title: String4 description: String5 isActive: Boolean6 └── questions/{questionId}7 questionText: String8 inputType: "text" | "number" | "email" | "phone" | "date" | "singleSelect" | "multiSelect"9 options: ["Option A", "Option B"] (select types only)10 isRequired: Boolean11 placeholder: String12 validationRegex: String (optional, e.g. "^[\\w.-]+@[\\w.-]+\\.\\w+$")13 order: int1415 questionnaire_responses/{responseId}16 questionnaireId: String17 userId: String18 answers: Map { questionId: answer }19 submittedAt: Timestamp2021PAGE STATE:22 answers: JSON Map = {}23 submitAttempted: Boolean = false2425WIDGET TREE:26 Column (padding: 16)27 ├── Text (questionnaire.title, Headline Small)28 ├── Text (questionnaire.description, Body Medium)29 ├── LinearPercentIndicator (answered count / total questions)30 ├── SizedBox (16)31 ├── Column (Generate Dynamic Children → questions ordered by order)32 │ └── Per question:33 │ Column34 │ ├── Text (questionText + " *" if required)35 │ ├── SizedBox (8)36 │ ├── Conditional Builder (inputType)37 │ │ text → TextField (placeholder)38 │ │ number → TextField (keyboardType: number)39 │ │ email → TextField (keyboardType: email)40 │ │ phone → TextField (keyboardType: phone)41 │ │ date → DateTimePicker42 │ │ singleSelect → RadioButton (options)43 │ │ multiSelect → CheckboxGroup (options)44 │ ├── Text (error msg, red, visible if invalid & submitAttempted)45 │ └── SizedBox (16)46 └── Button "Submit" (Primary, full width)47 On Tap:48 1. Set submitAttempted = true49 2. Validate required + regex50 3. If valid → Create questionnaire_responses doc51 4. Show SnackBar success or navigate to thank-youCommon mistakes when creating a Dynamic Questionnaire System in FlutterFlow
Why it's a problem: Not validating email and phone fields with regex before submission
How to avoid: Store a validationRegex on each question document and check it with RegExp.hasMatch before allowing submission.
Why it's a problem: Using Conditional Visibility toggles instead of Conditional Builder for input types
How to avoid: Use Conditional Builder which only builds the one matching widget, keeping the tree lean.
Why it's a problem: Hardcoding questions in the widget tree instead of loading from Firestore
How to avoid: Store all questions in Firestore and render dynamically so admins can modify questionnaires without app changes.
Best practices
- Use order values with gaps (10, 20, 30) so new questions can be inserted between existing ones
- Store validation regex on the question document so rules travel with the question
- Show a progress indicator based on answered questions to encourage completion
- Validate both required fields and regex patterns before allowing submission
- Use Conditional Builder instead of Conditional Visibility for input type switching
- Save anonymous user identifier when no authentication is required
- Add placeholder text to guide users on expected input format for each field
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to build a dynamic questionnaire in FlutterFlow that loads questions from Firestore. Each question has an inputType (text, number, email, date, singleSelect, multiSelect) and renders the matching widget. Answers are collected in a Map, validated with regex patterns, and saved to Firestore. Show me the schema, widget tree, and validation logic.
Create a page with a title, description text, a progress bar, a scrollable list of form fields, and a submit button at the bottom.
Frequently asked questions
How do I add conditional question branching?
Add dependsOn fields to each question document (dependsOnQuestionId, dependsOnValue). In the rendering logic, check if answers[dependsOnQuestionId] matches the required value before showing the question with Conditional Visibility.
Can I paginate questions across multiple screens?
Yes. Group questions by a pageNumber field and use a PageView to navigate between pages. Show only questions matching the current page number in each view.
How do I allow users to save partial progress and resume later?
Save the current answers Map to a draft_responses collection on a Save button tap or on page dispose. On return, load the draft and pre-fill the answers Map and input widgets.
Can I export questionnaire responses to a spreadsheet?
Create a Cloud Function that queries questionnaire_responses, maps each answer Map to column headers from the questions, and generates a CSV file for download.
What regex should I use for phone number validation?
A simple pattern like ^\+?[0-9]{7,15}$ accepts international phone numbers with an optional plus prefix and 7 to 15 digits. Adjust based on your target countries.
Can RapidDev help build a questionnaire platform?
Yes. RapidDev can build complete questionnaire systems with branching logic, multi-page navigation, response analytics dashboards, and CSV export features.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation