Possibly THE most common use case for Power Apps is building a data capturing experience. We’ll create lists or tables to save answers to questions for requests, audits and all kinds of other things. We commonly provide this experience using the Form control or adding individual controls to a screen. Both methods are great for small scenarios but soon become unmanageable & unscalable when adding more questions. Power Apps dynamic forms to the rescue.
When I started this blog in 2023 this was one of the first things I wrote down that I wanted to cover. I hope this article helps shed some light on the technique and gives you some good ideas for future developments. There’s a bit more configuration involved, but the long term benefits are awesome.
I’ve decided to split this into two articles; this one will go over the initial concept, getting start with the configuration & how we can build tabbed forms. The next will cover validation, saving the data & the administrative side, plus will include a downloadable solution for you to play around with!
Let’s get started with part 1.
Table of Contents
ToggleIntroduction
I’ll start off by saying that there’s nothing wrong with Form controls or manually adding & configuring individual controls. I’m a big fan of form controls (as my previous article hopefully shows), but there are limitations when it comes to larger scenarios. Power Apps dynamic forms helps us overcome some of these limitations, and some others too.
The technique involves storing a set of questions in a collection and showing them to users in a gallery control. Users will answer questions in the gallery, when finished the collection of information & responses will be saved. The collection can be rebuilt for editing responses too, using the same gallery control and Power Fx code.
So why is this technique important? I think it expands the art of the possible to give a more scalable, flexible design when up against form controls or applying & configuring controls separately.
Form controls
When we use a form control, we bind it to a list or table then add the columns we want the user to complete. Each column in a form control is represented by a data card and four controls:
So 5 controls per column; What if we have 50 questions we need to capture data for? That’s 5 x 50 = 250 controls just on the form and that’s before you get to the rest of the app’s content like galleries, buttons and so on.
There used to be a recommended limit of 300 controls per app documented here. This is to ensure performance is good – each component is its own mini package of code that needs rendering when the app/screen loads. More controls = longer render & load time. Scout around and there’s suggestions this limit is nearer 500 now, but we still don’t want to hog most or all of that with form control quirks.
The plus side, Form controls have a lot of cool stuff baked in for you – error handling, validation to name two. They’re a great starting point for beginners too, as there’s less configuration to do manually.
Individual controls
So adding individual controls helps instead then? Well yes, we’ll potentially reduce the number of controls compared to a form control. But that’s a ALOT of manual configuration for a large data capturing requirement, especially when it comes to the error handling side of things.
The plus side is you’ll get way more control on look and feel and everything gets handled exactly how you need it to. Some of the new modern controls will make things even easier for us too, as a single control can provide a header, title and whether it’s mandatory.
Where both struggle
Where both methods struggle a bit is for those larger scenarios. The higher number of controls and levels of config can be difficult to manage. Another key factor is the developer overhead for change. Let’s say your customer wants an additional column added – a very simple request.
1- Add the column into your list/table.
2- Open the app in the design studio. Configure your form to include the column or add individual controls.
3- Rewire any Power Fx formula to include the new column. Potentially add more code for validation or adding the new column to the right area of a tabbed form.
4- Save and publish the app.
5- Push through relevant Application Lifecycle Management process for testing.
Just to add a question in or make a small change to an existing one. This is another massive win for Power Apps dynamic forms, as we can hand off the above scenario to line-of-business colleagues without needing to edit the app! What if they had their own app or area to add or remove questions, or to change the order in which questions appear? That would keep our developers for the meatier updates or bug fixing & let our internal teams be WAY more self-sufficient.
Are Power Apps dynamic forms perfect? Of course not, but it’s a really solid method for data capturing in Power Apps. There’s more upfront development & configuration required, but you get that back with less to change & manage in the long run.
So enough talk. Let’s dive into the setup and the technicals to make all this happen.
The setup
Firstly, the scenario we’ll work with is to collate information needed for a new car insurance quote. Largely because that’s the last big set of questions I filled out before writing this article 😊.
To showcase Power Apps dynamic forms, we need two tables and a Power App. I say tables as I’m using Dataverse in my examples from hereon, but you can replicate with SharePoint lists or tables in any other data source.
The tables needed for this method are explained in more detail below.
Question data
This table will store the questions we want users to answer. It will store information about the question too, such as limits and the type of response needed (ie text, drop down date).
For each new response, this table of questions will be added into a new collection. User responses will save into that collection. The structure of the Question Data table like so:
Section: This column enables us to group questions into categories. This will make tabbed forms very easy to set up in our Power Apps dynamic forms.
Order: The order the questions should appear to the user, which should be unique for each question.
Question: The question we want to capture information for.
Mandatory: Determines whether the user must provide a response to the question. This is a simple Yes/No and will help to configure conditional functionality and validation of responses.
State: Whether the user must respond manually (state = open) or will be populated automatically (state = closed). This is optional but as we’ll see later, can be a good solution for niche functionality.
Answer Type: What format the response to the question should be, for example choice, text or rating.
Min/Max Length: Set boundaries for an Answer Type of Number or Text.
Choice Sets: To store choice values where the Answer Type is Choice. In this example, choices are saved as text separated by a semi-colon.
You can add pretty much any scenario to this structure – the columns stay the same! I’ve populated the table with some sample data so you can see a typical end goal.
User responses
We’ll need a table to capture user responses too. In this example I’ll store the responses as JSON in a multiple line of text field, so we only use a single row of data for each response. This technique to support Power Apps dynamic forms was another reason why I did this article a while back 😉.
Response ID: Each response will have its own unique ID. You can use the ready-made Dataverse or SharePoint unique IDs if you want, but I’ll be using something custom here.
Responder Name: The name of the person submitting the response. Users could be submitting multiple responses.
Responder Email: The email address of the responding user. This will allow easy filtering for showing user-specific entries for them to view or edit.
Response Text: This multiple line of text field will store all the Question Data config plus the columns that captured the users answers.
The initial build steps
Here I’ll detail all the nuts and bolts for how we work with those two tables, to create a Power Apps dynamic forms experience.
Generate a unique ID
For the purposes of this example, I’m creating my own unique ID for each response. This is a mixture of letters & numbers in a format of XXXX-XXXX.
I have an ‘Add a new response’ button for users to start a new response. Generating the unique ID is the first action in the button’s OnSelect property. The code below handles this for us:
// Generate a unique response ID.
// This is a mixture of letters & numbers in a format of XXXX-XXXX
UpdateContext(
{
cvUniqueId: $"{Concat(
ForAll(
Sequence(4),
Char(RandBetween(65,90))
),
Value
)}-{Text(RandBetween(1000,9999),"0000")}"
}
);
Build an empty response shell
Once the unique ID is generated, we’ll need to load the questions from the Question Data table. This provides our empty collection that will capture the users responses to each question.
Some additional columns are added during the processing. These are to store the unique ID generated, responses in the correct format and track any related errors.
/*
Build new, empty Question Set Table. Use question data saved in Dataverse, showing
only the columns we need. We can add columns at the same Time, as we need a couple of
additional fields to capture user responses to each question
*/
ClearCollect(
colUserResponse,
AddColumns(
ShowColumns(
// Only return the columns we need from our base Question Data tbl
'Question Data',
"section",
"question",
"questionorder",
"questionmandatory",
"questionstate",
"answertype",
"minlength",
"maxlength",
"choicesets"
),
// Adding additional columns:
// Unique response ID generated
"ResponseID", cvUniqueId,
// To store any text-based response
"ResponseText", "",
// To store any number-based response
"ResponseNumber", "",
// To store any date-based response
"ResponseDate", "",
// to capture errors real-time.
"ResponseError", false
)
);
Add and configure a gallery
For this, we are going to use a blank flexible height gallery. Some of our responses and controls will require more space than others, so we need the height to be variable.
In the Items property of the gallery, we’ll reference the colUserResponse collection built above. We need to ensure the information is always sorting by our defined ordering column, in ascending order.
/*
Use blank shell collection ready for new submission.
Sort by defined Question Order, ascending
*/
SortByColumns(
colUserResponse,
"questionorder",
SortOrder.Ascending
)
The Template Size property of my gallery is 60, but feel free to work with what’s best for you.
Adding controls
Next, we need to add a label to display the question to the user. If the question is mandatory in the Question Data table, we’ll want to indicate that too. Here’s the Power Fx for the Text property of my label:
// Show question. If mandatory, add an asterick
$"{ThisItem.question} {If(
ThisItem.questionmandatory = "Yes",
" *"
)}"
As we’d expect with a gallery, one control is used to show a column value, yet will show as many times as there are rows in the underlying data. Our Power Apps dynamic form quickly takes shape:
We now need to add controls for each possible Answer Type in our Question Data table. Let’s start with free text.
Add a Text Input control to the gallery. Set the visible property to only show when the Answer Type column equals Text:
// Only show control when answer requires text
ThisItem.answertype = "Text"
We’ll need to repeat this for every possible response type we need. Each control needs its Visible property configured to only show when the Answer Type column value equals the relevant control.
As we can see from the image above, each relevant control type is added once to our gallery. They’re all aligned to the same X & Y co-ordinates but only show when their respective visibility is triggered.
A point to note: I have separate text input controls for anything email or telephone related. You can use the same single text input control for these and any other free text entries. I prefer to separate them out in order to ringfence their validation (more on that in part 2).
Control configuration
To get each control production-ready, we need to make a few tweaks. We also need to tell each control to update our underling colUsersResponse collection, every time a user adds a response.
There are a lot of code snippets below. There is a fair bit of config required to get a Power Apps dynamic form set up. Honestly, once you’ve done this technique a few times you’ll get through all of the below steps really quickly.
Text input
Set the Default property to the ResponseText column of the collection:
/*
Default to ResponseText Column. This is needed when we reload a
previous response so we can view or edit
*/
ThisItem.ResponseText
We can use our text input control for either single, or multi lines of text. Firsty, set the MaxLength property to the following:
// If no max length stated in Question Data, assume a default of 255
If(
IsBlank(ThisItem.maxlength),
255,
ThisItem.maxlength
)
Then, set the Mode property to:
/*
If max length is greater than 255, text control needs to
be in Multi Line mode, else Single Line mode
*/
If(
ThisItem.maxlength > 255,
TextMode.MultiLine,
TextMode.SingleLine
)
If multi line is triggered, maybe we’ll want the text input control to be bigger. It might not be a great user experience otherwise. I’ve added the following to the Height property of the main text input control:
If(ThisItem.maxlength > 255, 120, 40)
When users add a response, we need to update the underlying collection to store what they’ve entered. We need to make sure the answer is stored against the relevant question, and for the relevant data type. As the Question Order number should be unique, we can use that as our identifier for the right row to update.
We’ll therefore need the following code in the OnChange property of the text input control:
/*
Save user response to question against relevant row in collection.
Ensure response is saved in relevant response column (Text, Number or Date)
*/
UpdateIf(
colUserResponse,
questionorder = ThisItem.questionorder,
{ResponseText: Self.Text}
);
The final property update for the text input control is a very small but important one. If a user types their text, the OnChange above will fire with every single keystroke. That might not be optimal, so change the DelayOutput property from false to true:
Remember – this is one control that will serve ALL responses that require free text. The above config will kick in for each relevant row.
Number input
Set the Default property to the ResponseNumber column of the collection:
/*
Default to ResponseNumber column. This is needed when we reload a
previous response so we can view or edit
*/
ThisItem.ResponseNumber
Update the OnChange property to capture the users response into the ResponseNumber column for the relevant question:
/*
Save user response to question against relevant row in collection.
Ensure response is saved in relevant response column (Text, number or Date)
*/
UpdateIf(
colUserResponse,
questionorder = ThisItem.questionorder,
{ResponseNumber: Value(Self.Text)}
)
As with the text input control, update the DelayOutput property to true.
Choice input
In my example, all my drop down boxes are storing text values for users to select. Therefore, we’ll need to update the Default property of the drop down control to reference ResponseText. Adjust accordingly depending on the data you’re showing and saving.
/*
Default to ResponseText Column. This is needed when we reload a
previous response so we can view or edit
*/
ThisItem.ResponseText
It will be very similar for the OnChange property; make sure you’re updating the correct response type column to match the data type:
/*
Save user response to question against relevant row in collection.
Ensure response is saved in relevant response column (Text, number or Date)
*/
UpdateIf(
colUserResponse,
questionorder = ThisItem.questionorder,
{ResponseText: Self.Selected.Value}
)
For the Items property of the drop down control, we’ll reference the ChoiceSets column in the collection. This has our choices for relevant questions stored as a string, for example:
Third party;Third Party Fire & Theft;Comprehensive
We’ll use the Split function to convert these entries into a table-like structure that the drop down (or combo box) needs. Whilst doing so, we’ll add a custom option for ‘Please select’ at the top. We can use this for validation purposes later. Here’s the Power Fx for the Items property of the drop down control:
// Create object from semi-colon delimited string, add a custom option in transit
Ungroup(
Table(
// Append default option of Please select
{OptionSet: ["Please select"]},
// Split ChoiceSets string by semi-colon to create option tbl
{
OptionSet: Split(
ThisItem.choicesets,
";"
)
}
),
"OptionSet"
)
Radio input
Set the Default property to the ResponseText column of the collection:
/*
Default to ResponseText Column. This is needed when we reload a
previous response so we can view or edit
*/
ThisItem.ResponseText
The Items property for a radio control uses the same Split logic that’s used for a drop down control:
// Items for Radio control
Split(
ThisItem.choicesets,
";"
)
Finally, as radio controls list items vertically, we need to dynamically set the height. Otherwise, users may need to scroll through the radio options.
Add this to the Height property of the radio control:
// Height is number of entries, multiplied by chosen height px
CountRows(
Split(
ThisItem.choicesets,
";"
)
) * 50
The OnChange logic for a radio control is exactly the same as for the Choice input.
Date input
Update the DefaultDate property to the ResponseDate column of the collection:
/*
Default to ResponseDate column. This is needed whe we reload a
previous response so we can view or edit
*/
DateValue(ThisItem.ResponseDate)
As you might then expect, the OnChange property needs to store the response for the related question in the ResponseDate column of the collection:
/*
Save user response to question against relevant row in collection.
Ensure response is saved in relevant response column (Text, number or Date)
*/
UpdateIf(
colUserResponse,
questionorder = ThisItem.questionorder,
{ResponseDate: Self.SelectedDate}
);
Rating input
The output of a rating control is numeric, so the Default property needs to reflect that:
/*
Default to ResponseNumber column. This is needed when we reload a
previous response so we can view or edit
*/
ThisItem.ResponseNumber
With that in mind, the OnChange property will point to the ResponseNumber column of the collection too:
/*
Save user response to question against relevant row in collection.
Ensure response is saved in relevant response column (Text, number or Date)
*/
UpdateIf(
colUserResponse,
questionorder = ThisItem.questionorder,
{ResponseNumber: Self.Value}
)
Initial form done!
That’s the base config of our form done! As I said above, it might look like a fair bit of setup but after a couple of run through’s it’ll get pretty quick. If you use the same table structure each time, you could even make this a component to use across multiple apps.
You can go to town with styling, how many questions per row etc but for this article, we’re focusing primarily on the configuration steps. You can apply the same logic to any control you want to include in the form:
1- Ensure the Answer Type specifies the control.
2- Add the control to your gallery and update the Visible property to match.
3- Make sure the OnChange property is configured to capture the response.
4- Make sure the Defauly property is configured to show the captured response.
Handling niche requirements
In our scenario, we’re capturing information relating to new car insurance quotes. We want the users to populate a date of birth and the age be calculated automatically. I have some optional configuration in my Question Data table – State. This is set to open if we want the user to provide a response, or closed if it’ll be generated automatically.
As well as State = Closed, we can set a minimum and maximum for the value calculated for Age. You have to be a minimum of 17 to drive in the UK, there isn’t a maximum but for demo purposes I’ve set it to 50:
For the DisplayMode property of each control in our form, we can add the following Power Fx:
/*
If user needs to respond - open
If they don't, or will be done automatically - closed
*/
If(
ThisItem.questionstate = "Open",
DisplayMode.Edit,
DisplayMode.Disabled
)
This will disable any control that will be populated automatically:
In my gallery, I’ve added a new date picker control, which will only be visible when the underlying Answer Type is Birthday. We want to ringfence this control to update the Age column, and leave the other date picker to update other date fields:
We’ll use the OnChange property of this control to update the Age field. The code is a bit longer to handle this niche requirement so at high level it will:
1- Update the collection to capture the date selected by the user. It will save the response for the Date of Birth question.
2- Perform a calculation to determine the age in years, identifying the difference between the date selected & today’s date.
3- Determines if the calculated age is between the minimum & maximum number range defined in the question (in our case, between 17-50). This will return a true (out of range) or false (inside range).
4- Update the collection for the Age question. Store the calculated age but also the true/false value in step 3, so we can keep track of any errors in the submission.
Here’s the Power Fx in the OnChange property of the dedicated date picker for capturing the birthday:
// Update Date of Birth
UpdateIf(
colUserResponse,
questionorder = ThisItem.questionorder,
{ResponseDate: Self.SelectedDate}
);
With(
{
// Calculate age by using selected date and today's date
tvAgeCalc: If(
DateDiff(
Today(),
Date(
Year(Now()),
Month(Self.SelectedDate),
Day(Self.SelectedDate)
)
) <= 0,
DateDiff(
Self.SelectedDate,
Today(),
TimeUnit.Years
),
DateDiff(
Self.SelectedDate,
Today(),
TimeUnit.Years
) - 1
)
},
// Check if age is within the boundaries specified
With(
{
tvAgeValidation: If(
Or(
tvAgeCalc < ThisItem.minlength,
tvAgeCalc > ThisItem.maxlength
),
true,
false
)
},
// Update Age row with age value & outcome of age check (as boolean)
UpdateIf(
colUserResponse,
question = "Age",
{
ResponseNumber: tvAgeCalc,
ResponseError: tvAgeValidation
}
)
)
)
Watch closely in the gif below; populating the Date of Birth also populates the Age for us:
Tabbed forms
Whilst not mandatory for Power Apps dynamic forms, creating tabs to group together bunches of questions is really handy. If you’ve done this with either form controls or individual controls, you’ll know it’s quite tedious to set this up. You have to update the Visible property of every control/groups of controls and it can be difficult to maintain.
The beauty of having all of our questions in a collection means we can simply filter where column equals value. Any new questions getting added? An existing question changes category? No worries – it’ll still be wrapped up in the same filtering logic. No app edits required.
To begin with, we can create our tabs as a collection or simply add the logic directly to the Items property of a gallery. We want to take every entry in the Section column of our table, then get a single value of each entry. We do that using the Distinct function. Here’s the Power Fx for the Items property of my tabs gallery:
// Build tabs by getting distinct values from Section column of Question Data
Distinct(
SortByColumns(
colUserResponse,
"questionorder",
SortOrder.Ascending
),
section
)
I’ve added a button to the gallery. I now have some tabs!
The text property of the button is set to ThisItem.Value. The OnSelect property needs to set a variable so we can use that to filter our Power Apps dynamic form.
// Value is Section according to Question Data
UpdateContext({cvFormTab: ThisItem.Value})
Finally, we can update the Items property of the gallery that holds our dynamic form. We need to show ALL records, OR where there’s a match for a specific tab being selected:
/*
Filter dynamic form collection by selected tab
Show all if no selection has been made
*/
SortByColumns(
Filter(
colUserResponse,
section = cvFormTab || IsBlank(cvFormTab)
),
"questionorder",
SortOrder.Ascending
)
We now have a tabbed form:
That concludes Part 1, where we’ve covered initial setup, example of handling niche requirements and creating a tabbed form experience.
In Part 2, I cover:
1- building various ways to validate entries.
2- optimal ways to save & reload a response for editing.
3- adding & editing questions without having to edit & republish the app.
You can find Part 2 here, which includes a link to a FREE sample canvas app solution available that you can download and use as a starting point.
If you liked this article and want to receive more helpful tips about Power Platform every week, don’t forget to subscribe or follow me on socials 😊
What about look up field? Can we also ad that field also in this dynamic form with its look-up data?
Hey Keyur, sure you can do lookups too! Obviously it depends on what control type you want to implement in but absolutely. You can dedicate a control to a lookup output, or perform the lookup based on a specific question. For example, you could set the Default value of a text input control to run a condition to pull a value, else leave blank:
If(QuestionID = 1, Lookup(Data source, Condition).Value, “”)
Alternatively you can use a drop down or combo box to filter records from other sources to present to the user. Either with a dedicated control that only shows with a specific question, or by using similar Lookup logic for the Items property:
If(QuestionID = 1, Filter(Data source, Condition), [“Option 1″,”Option 2”]
Excelente trabajo
Hi Craig, Love your work!
I’m a total noob with PowerApps but this dynamic form is exactly what I’m seeking to build! Where would I drop the ‘Build an empty response shell’ code into? TIA
Hi Paul! Building the empty response shell – two options.
1) if your users are only ever going to submit/capture x1 response in a session, you can get away with building the empty shell in the OnStart property of your app. However, if they’re likely to submit and/or edit multiple responses in a single session, then…
2) I’d typically have a screen (ie a homepage) that lists all user responses. They can either click on an existing item to rebuild the collection to perform edits, or click on a ‘New Response’ button and use the OnSelect property to create the empty shell.
Option 2 preferred, as means you’re only likely to have all the code in 1 place.
Hi Paul,
Thanks for this great walkthrough, amazing to see what can be achieved with some ingenuity in this platform!
I was wondering if you had ever thought about integrating the pen input or an attachment input into the dynamic form. I have a use case for both of these but I’m finding it quite difficult to know where to begin when it comes to these input methods and dynamic forms.
Would be grateful to hear your approach to these/if it is even possible.
Hi Scott,
For pen input, I can capture the initial input and save as an image, but not sure we can reload it back into a pen input control for editing (though not sure that would be a healthy/normal use case?).
Attachments wise, in the past I’ve had a secondary table/library/list to store them. I’ll have a separate gallery/control to capture the attachments then, once the main response is submitted & unique ID captured, I’ll use that ID to patch attachments back to a separate table/library/list. This might be possible to do directly in Dataverse with the File column type but I’ve not tested it.
Awesome concept!
I have the requirement to dynamically show/hide certain questions based on some answers/selections.
Is this technically possible, or is there something that might prevent it?
Hi Reto,
Thanks for the question, and yes I believe it is. I would tackle it like so:
1) Have an additional column in the Question Data table for Visible. Each question has a true or false value.
2) For your parent question, this will be true. False for any conditional questions.
Question | Response Type | Visible
Do you have a dog? | Yes/No | True
What breed is it? | Choice | False
3) In your app, add a filter to only include questions where Visible column is true.
4) In the control you’re using for the visible questions, configure the OnChange property to run a condition. If the condition is met, patch the row of the child record to change the Visible column to true.
Question | Response Type | Visible
Do you have a dog? | Yes/No | True
What breed is it? | Choice | True
5) You may also need to reset the child response, if someone changes the parent question back to an option that does NOT invoke the condition.
It’s probably not going to be too far away from the steps in the ‘handling niche requirements’ section of the blog.
Let me know how you get on and if you need more help, just shout!
Hi Craig,
Great post! I have a requirement to build something dynamically based on configuration, but I’m facing some challenges with the parent-child question setup.
Could you provide some guidance?
Hi Karuna, what are the challenges you’re facing and I’ll see if I can help!
Hello,
Very nice setup. I was looking and started to build one but this is clearly explained.
What I was wondering, it would be difficult to manage to “copy” this on different apps, did you already tried to add it in a component library?
I’m not yet confident with the library so maybe I miss something important in here…