[SPFx] Form Customizer Boilerplate
Since the v1.15 of SharePoint Framework, we can (finally) customize list forms! Until now, the only customizable options were through PowerApps or limited to switch fields or display / hide them.
But actually, the customization leads to a gap with the native display, as clicking on "New" / "Edit" / "Display" redirects you to a dedicated SPFx hosting page. Even if there're plans to make custom forms available through the well-known side panel, I though it could be interesting to give a starting point solution, that would less impact users experience 🙂
It's also interesting to remind the existance of the ETag, which prevents concurrency updates on existing list items.
Prerequisites
- An Office 365 (Dev) Tenant or a Partner Demo Tenant
- SPFx 1.15 package (at least) installed on the local machine
The whole project snippet is available here.
For this example, we also use the following libraries:
For the package deployment & Content Type component association, we use the PnP PowerShell module.
Deep dive in code
Form customizer declaration
First, let's have a look at the init method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
1 2 3 4 5 6 7 8 9 | |
this.context, as we know it, contains know properties (because it's inheriting from BaseComponentContext) such as instanceId, pageContext,... But here as a FormCustomizerContext, we can get info related to the list context:
listcontentTypefolderInfoitemIditem(new to SPFx 1.16)domElement
We also have list form context info, such as displayMode that let us know if we're in New, Display or Edit state (but this info is also in the URL as query parameter 😬).
So here, we are loading the list item if we're displaying an existing one, through the _loadItem method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Once our data loaded, we just have to push it to our Form customizer component!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Component
Now let's have a look at the component!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | |
In the return statement of the render method we got interesting stuff:
- the
formtag is used to control the required fields with native HTML5 behavior - the
CommandBar, theSeparatorand theBreadcrumbcontrols placed here are for keeping consistency to the UX, as we're moving to theSPListForm.aspxSPFx hosting page
Let's keep scrolling down!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | |
A few other things to point out:
- As the
SPListForm.aspxhosting page needs context parameters (such as the PageType or the ID) to know what to display, the_onClickEditItemevent redirects from the Display mode to the Edit mode - the
_renderSaveButtonmethod renders aPrimaryButtonwith thetypeattribute submit, which is crucial when working in aformcontext (and not declared as such by default in the Fluent UI component) - the
_onSubmitSaveItemevent triggered by the submit button mentioned before contains theevent.preventDefault()statement, in order to not redirect the user (because it has to be handled by an inherited event declared in theFormBoilerplateFormCustomizer, we'll see about that later) - this method sends the updated form data to the host, which will perform the update operation and redirect the user to the list view or catching the error to display to him
Below the form customizer "as is" when initiated by Yeoman:

As we can see, the default display is really...empty! You can also see that the SharePoint App Bar isn't displayed here.
So the idea was to keep the same UI as the one we have in the list form:

Based on this, here's my proposal of Form customizer header:

Now, back to the FormBoilerplateFormCustomizer host:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | |
Here, we got the following:
- Cleaning the Person field info as SharePoint REST API expects a specific format when adding / updating it
- Depending if it's a new or existing item, we add the known ETag to the request
- When working locally on the component, using the
DEBUGproperty is usefull, because when triggering thethis.formSaved(), theSPListForm.aspxhosting page redirects to the site home page instead of the target list, which could be frustrating
A React control to rule them all
Since PnP SPFx Controls version 3.10, there's a new control called DynamicForm, which could perfectly fit to SPFx latest feature. Let's give a try!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | |
As we can see, it's shorter than the first approach! With a "few" lines we have a ready-to-use Form customizer!
But we notice that:
- there's neither
CommandBar,SeparatororBreadcrumbcomponents, since the Save and Cancel buttons are part of theDynamicFormcomponent, we can't hide them or overriding them - we can't handle the ETag here, as the component doesn't have a prop which allows us to specify it
- the saving and cancelling events are handled by the component (through the
onSubmittedand theonCancelledevent props)
Th advantage of this component is that it handles the list context, so that the fields are displayed accordingly to its host (with associated configuration for each of them). But we're limited into the UI (even if there's a render overriding method called fieldOverrides), especially if we want to keep existing parts like the CommandBar.
Deployment
In order to deploy our Form customizer properly, below the script used to deploy the solution to the tenant app catalog and associate the component to the list content Type.
Info
The list used here is the Employee onboarding template provided by Microsoft when creating a list:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
And that's it! From this boilerplate, you should be able to start playing with the Form Customizer!
Happy coding! 😄