Installation & Activation
Deploying the .sppkg package to the App Catalog and activating the form on a list.
Requirements
- SharePoint Online with the modern list experience. The solution is built on SPFx 1.23 (
FormCustomizerextension andListViewCommandSet). - Deployment roles: App Catalog administrator (tenant or site collection) for deployment, and site owner for activation on a list. For PowerShell scripts, the
PnP.PowerShellmodule (Install-Module -Name PnP.PowerShell -Scope CurrentUser). - Building the package: Node.js
>=22.14 <23and dependencies installed in both packages (spfx-form-customizerandspfx-form-customizer-core). A pre-built.sppkgdoes not require Node.
Building the Package
If you already have a pre-built .sppkg from your vendor, skip directly to Deploying the Package.
- In both
spfx-form-customizer-coreandspfx-form-customizerfolders, runnpm install(the packages do not use workspaces; each has its ownnode_modules). - In the repository root, run
npm run ship. The command syncs the core package, runs unit tests, cleans old outputs, and builds the production package. - The output is
spfx-form-customizer/sharepoint/solution/spfx-form-customizer.sppkg. Scripts and styles are bundled inside the package (includeClientSideAssets) and SharePoint serves them from the App Catalog CDN; no external storage is required.
Before releasing the entire repository, also run npm run verify (core check, tests, documentation). The release process is described in Release (Czech).
Deploying the Package
Upload the package to the App Catalog. Choose one of the two options below.
Tenant App Catalog (Recommended)
- Open the SharePoint Admin Center (
https://<tenant>-admin.sharepoint.com), go to Apps and then App Catalog (or directlyhttps://<tenant>.sharepoint.com/sites/appcatalog). - Upload
spfx-form-customizer.sppkgto the Apps for SharePoint library. - In the dialog, check Enable this app and add it to all sites. The package has
skipFeatureDeployment: true, so it is deployed tenant-wide without needing to add it to individual sites. - Confirm by clicking Enable app.
Site Collection App Catalog
If you do not have access to the tenant App Catalog, a site collection administrator can create a dedicated App Catalog (Add-PnPSiteCollectionAppCatalog) and upload the package there. The solution will then only be available on sites within that site collection.
Storage Entities (license, script policy) are read via /_api/web/GetStorageEntity, which may return a value from a site collection App Catalog before the tenant one. A site collection administrator with their own catalog could therefore enable scripts for their site. See details in Script Policy.
Activating the Form on a List
Deploying the package alone does not change anything on lists. You must enable the form on a list using one of the methods below. Activation sets the CustomFormCustomizer component (ID 910d1ea6-1bac-43f1-8f31-01aeba0c3605) as the New, Edit, and Display form for the selected content types.
"Custom form" / "Open FormCraft 365 editor" Button (Form Activator)
The package includes the FormActivatorCommandSet extension, which adds a command to the list command bar for managing the custom form (replacing PowerShell) and, when custom list actions are configured, also adds buttons to the command bar and context menu.
- Open the list and on the command bar click Custom form (if a custom form is not yet active on the list) or Open FormCraft 365 editor (if a custom form is already active). Only site owners see this command (checked via
ManageWebpermission, or designer group membership). Visibility and activation status are determined immediately when the command bar loads from page permissions and cached for the browser session (sessionStorage) so the bar does not flicker; actual permissions are always verified when loading and saving the configuration. If the form is already active, the button opens the form designer directly (RibbonBuilderHost), where you can edit the form and manage activation via the List Activation button. - In the Custom Form Activation dialog, optionally select another list on the site (lists with template 100, 120, and 1100 are offered; document libraries are not offered).
- Choose which modes the form should be used for: New item (NewForm), Edit item (EditForm), Display item (DisplayForm).
- Check the list content types (hidden types and folders are not offered) and click Activate custom form. The dialog also offers two options (enabled by default): Disable offline availability of the list (without this, list buttons and actions may not appear for users with Microsoft Lists sync; the list will not be available offline) and Disable quick edit (grid view) (prevents editing outside the form). FormCraft rules (required fields, locks, calculations) only apply inside the form; edits via Excel, Power Automate, API, or the mobile app will only be constrained if rules are configured directly on SharePoint columns. Failure to update these list settings is only a warning. When deactivating, you can use Restore offline availability and quick edit to revert both settings. For already active types, Update settings and Deactivate (restore default SP form) buttons are available.
If the configuration is protected by an editor lock password, the dialog prompts for the password before making changes or deactivating. You can also open this same dialog from the designer via the List Activation button.
The command label on the bar is localized according to the SharePoint language (English, Czech, German, Slovak; other languages receive the English label).
Activation only affects the forms you enable. Any binding of another customizer on a form you do not select remains unchanged, and deactivation only removes the binding to the Form Customizer component. Current content type states are loaded before making changes. If a change fails mid-way, the dialog rolls back already modified content types or lists the types that remained modified in the error message.
SharePoint is a single-page application and caches form bindings in page memory, so activation and deactivation only take effect after a full page reload. After a successful change, the dialog displays a notice with a Reload page now button and automatically reloads the page when closed. When opened from the designer, the dialog does not auto-reload the page (so you do not lose unsaved configuration), though the button remains available.
The commands are registered in the package as two separate components: for the command bar (CommandBar, component FormActivatorCommandSet) and for the context menu (ContextMenu, component FormListActionsContextMenu, since version 1.28.1) on lists with templates 100 (Generic List), 120 (Custom List), and 1100 (Issues List)—the same templates offered by the dialog. Document libraries (101) do not have the command. After upgrading to 1.28.1 or from a version older than 1.26.0, check the entries in the Tenant Wide Extensions list (delete old context menu entries, add missing ones, verify none are disabled; see Checking Tenant Wide Extensions Entries); otherwise, command bar buttons will appear duplicated and the admin command "Custom form" / "Open FormCraft 365 editor" will also appear in the context menu. To enable the form on a different list type, use PowerShell or the dialog from the designer. If the command does not appear on the bar, verify that tenant-wide deployment is enabled in the tenant App Catalog, or activate the spfx-form-customizer Feature on the site (Site Settings, Manage site features).
PowerShell (Manage-FormCustomizer.ps1)
For bulk management, the repository includes the script scripts/Manage-FormCustomizer.ps1 (supports both PnP.PowerShell 1.x and 2.x). When run, it connects interactively to the site (Connect-PnPOnline -Interactive) or reuses an existing connection to the same URL. It sets the NewFormClientSideComponentId, EditFormClientSideComponentId, and DisplayFormClientSideComponentId properties on the list's content types (excluding folders 0x0120) via Set-PnPContentType, just like the "Custom form" dialog.
| Parameter | Description | Default Value |
|---|---|---|
-SiteUrl | Site URL (required) | – |
-ListTitle | List title or GUID (required) | – |
-Action | Status, Enable, Disable | Status |
-ComponentId | Component GUID from manifest | 910d1ea6-1bac-43f1-8f31-01aeba0c3605 |
-ClientId | Entra ID app Client ID for Connect-PnPOnline -Interactive (PnP.PowerShell 2.x) | preset in script |
# Check status (NATIVE SHAREPOINT / ACTIVE / OTHER COMPONENT for each form)
.\scripts\Manage-FormCustomizer.ps1 -SiteUrl "https://contoso.sharepoint.com/sites/intranet" -ListTitle "Poptavky" -Action Status
# Activate for NewForm, EditForm, and DisplayForm
.\scripts\Manage-FormCustomizer.ps1 -SiteUrl "https://contoso.sharepoint.com/sites/intranet" -ListTitle "Poptavky" -Action Enable
# Revert to native forms (empty GUID)
.\scripts\Manage-FormCustomizer.ps1 -SiteUrl "https://contoso.sharepoint.com/sites/intranet" -ListTitle "Poptavky" -Action Disable
The script configures forms at the list level, while the "Custom form" dialog configures them at the list content type level. Therefore, the status may not display identically between both methods in the dialog. Stick to one method per list and verify the status using -Action Status.
Files in SiteAssets
Form configurations are stored in the Site Assets library of the site containing the list. The designer creates the SiteAssets/FormCustomizer folder on the first save. How this folder is secured is described in Permissions.
| Path | Contents |
|---|---|
SiteAssets/FormCustomizer/<listId>/config.json | Current form configuration. The only file read by the form runtime. |
SiteAssets/FormCustomizer/<listId>/backup.json | Previous working configuration (automatic backup on every save, used by "Restore from backup"). |
SiteAssets/FormCustomizer/<listId>/history/ | Last 10 configuration versions as <listId>.<timestamp>.json files. Restoring from history does not have a UI button yet; paste the file contents into the Raw JSON panel (see Troubleshooting). |
SiteAssets/FormCustomizer/license.txt | License key saved using the "This site collection" option (see Licensing). |
Older versions stored configurations in the list property bag and in FormCustomizer/<listId>.json. The form runtime no longer reads these. The designer reads them once for a site owner so you can migrate them by saving.
Upgrading to a New Version
- Before building, increment the version in
spfx-form-customizer/config/package-solution.json: bothsolution.versionandfeatures[0].version(formatmajor.minor.patch.build, e.g.,1.9.0.0to1.9.1.0). Without a higher version, SharePoint will not accept the new package as an upgrade. - Build the package by running
npm run ship. - In the App Catalog, upload the new
.sppkgand confirm replacing the existing one. For tenant-wide deployment, confirm Enable app again. - After refreshing the page (or clearing the browser cache), the new version is active. Configurations in
SiteAssets, the license key, and the script policy remain unchanged during an upgrade. - Upgrading the package (uploading a new
.sppkgwith add to all sites) does not modify or delete existing Tenant Wide Extensions entries; it only adds missing ones. Always verify after upgrading that the list contains exactly the 6 entries from the table and none are disabled (Disabled = Yes); see Checking Tenant Wide Extensions Entries.
Checking Tenant Wide Extensions Entries (Upgrading to 1.28.1 and from Versions Older Than 1.26.0)
Since version 1.28.1, the Command Set extension consists of two separate components: FormActivatorCommandSet (command bar: "Custom form" / "Open FormCraft 365 editor" command and command bar list actions) and FormListActionsContextMenu (item context menu: context menu list actions only). Each is registered for list templates 100, 120, and 1100. This makes 6 entries in total in the Tenant Wide Extensions list in the App Catalog.
During an upgrade, SharePoint adds entries whose names are missing from the list; it does not modify or delete existing entries, so obsolete ones must be deleted manually. Always verify after upgrading that the list contains exactly the 6 entries from the table and that none have Disabled = Yes (SharePoint does not deliver disabled entries at all, so the corresponding command will not run). Missing or disabled entries will cause the "Custom form" / "Open FormCraft 365 editor" button not to appear on the command bar. Obsolete entries will cause duplicate buttons on the command bar and the admin command to appear in the context menu.
Expected entries (Sequence 0, Disabled No, Web Template and Host Properties empty):
| Title | Component Id | Location | List Template | Component Properties |
|---|---|---|---|---|
| FormActivatorCommandSet-100 | 4a8b7c12-5d3e-4f9a-8b1c-6e2d9f4a3b5c | ClientSideExtension.ListViewCommandSet.CommandBar | 100 | {"surface":"commandBar"} |
| FormListActionsContextMenu-100 | a9998067-8e06-423f-b8a0-3177988f3a42 | ClientSideExtension.ListViewCommandSet.ContextMenu | 100 | {} |
| FormActivatorCommandSet-120 | 4a8b7c12-5d3e-4f9a-8b1c-6e2d9f4a3b5c | ClientSideExtension.ListViewCommandSet.CommandBar | 120 | {"surface":"commandBar"} |
| FormListActionsContextMenu-120 | a9998067-8e06-423f-b8a0-3177988f3a42 | ClientSideExtension.ListViewCommandSet.ContextMenu | 120 | {} |
| FormActivatorCommandSet-1100 | 4a8b7c12-5d3e-4f9a-8b1c-6e2d9f4a3b5c | ClientSideExtension.ListViewCommandSet.CommandBar | 1100 | {"surface":"commandBar"} |
| FormListActionsContextMenu-1100 | a9998067-8e06-423f-b8a0-3177988f3a42 | ClientSideExtension.ListViewCommandSet.ContextMenu | 1100 | {} |
Upgrading to 1.28.1 and newer (BUG-23): up to version 1.28.0, context menu entries used the same Component Id as the command bar and differed only by the {"surface":"contextMenu"} property. Since 1.28.1, the context menu has its own dedicated component (a cleaner solution; the suspected cause of intermittent command bar behavior was actually different—see Troubleshooting: Microsoft Lists sync and accidentally disabled entries). SharePoint added three new entries FormListActionsContextMenu-100/120/1100 during the upgrade. Do not edit the old FormActivatorCommandSet-ContextMenu-100/120/1100 entries—delete them; keeping or editing them creates duplicate registrations. Until you delete them, context menu actions will not appear (the old component ignores them), while the command bar works properly.
Browser procedure:
- Open the list
https://<tenant>.sharepoint.com/sites/<appcatalog>/Lists/TenantWideExtensions/AllItems.aspx(App Catalog, Site Contents, Tenant Wide Extensions). - Add the columns Component Id, Location, List Template, and Component Properties to the view and filter by Component Id
4a8b7c12-5d3e-4f9a-8b1c-6e2d9f4a3b5c. - Upgrading to 1.28.1 and newer: delete the old context menu entries—those with Location
ClientSideExtension.ListViewCommandSet.ContextMenuwhose Component Id is4a8b7c12-5d3e-4f9a-8b1c-6e2d9f4a3b5c(namedFormActivatorCommandSet-ContextMenu-<template>). SharePoint added the newFormListActionsContextMenu-<template>entries with Component Ida9998067-8e06-423f-b8a0-3177988f3a42automatically; if they are missing from the list, create them. - Add any missing entries using the New button according to the table above. Verify that no entry has Disabled = Yes.
- Delete obsolete entries: those whose Location is only
ClientSideExtension.ListViewCommandSet(without the.CommandBar/.ContextMenusuffix), command bar entries with empty Component Properties, and any entries not listed in the table. Go by the Location and Component Id columns, not the title. - Changes take effect on sites within approximately 20 minutes; afterwards, refresh the page (Ctrl+F5). The "Custom form" / "Open FormCraft 365 editor" command should appear on the command bar only once and never in the context menu.
Alternative: remove the app from the App Catalog including the recycle bin, and upload it again with add to all sites checked. SharePoint will recreate the entries with the correct Component Ids. During that time, forms will be unavailable across all sites; configurations in SiteAssets remain preserved.
The same via PnP PowerShell (first run only the listing; make changes only after reviewing). The default PnP Management Shell app has been retired, so you must specify your own app registered in your Microsoft Entra ID tenant for -ClientId:
Connect-PnPOnline -Url https://<tenant>.sharepoint.com/sites/<appcatalog> -Interactive -ClientId <your-app-id>
$list = "Lists/TenantWideExtensions"
$barId = "4a8b7c12-5d3e-4f9a-8b1c-6e2d9f4a3b5c" # FormActivatorCommandSet (command bar)
$ctxId = "a9998067-8e06-423f-b8a0-3177988f3a42" # FormListActionsContextMenu (context menu, since 1.28.1)
$bar = "ClientSideExtension.ListViewCommandSet.CommandBar"; $ctx = "ClientSideExtension.ListViewCommandSet.ContextMenu"
$mine = { Get-PnPListItem -List $list | Where-Object { $_["TenantWideExtensionComponentId"] -in @($barId, $ctxId) } }
# 1) List: ID | Component Id | Location | template | properties
& $mine | ForEach-Object { "{0} | {1} | {2} | {3} | {4}" -f $_.Id, $_["TenantWideExtensionComponentId"], $_["TenantWideExtensionLocation"], $_["TenantWideExtensionListTemplate"], $_["TenantWideExtensionComponentProperties"] }
# 2) Upgrade to 1.28.1+: DELETE old ContextMenu entries with the command bar Component Id (do not edit; new ones are created by SharePoint / step 4)
& $mine | Where-Object { $_["TenantWideExtensionComponentId"] -eq $barId -and $_["TenantWideExtensionLocation"] -eq $ctx } |
ForEach-Object { Remove-PnPListItem -List $list -Identity $_.Id -Force }
# 3) Delete obsolete entries (Location without suffix, command bar entries without properties)
& $mine | Where-Object { $_["TenantWideExtensionLocation"] -eq "ClientSideExtension.ListViewCommandSet" -or
($_["TenantWideExtensionComponentId"] -eq $barId -and -not $_["TenantWideExtensionComponentProperties"]) } |
ForEach-Object { Remove-PnPListItem -List $list -Identity $_.Id -Force }
# 4) Add missing entries (existing ones are skipped)
$existing = & $mine
foreach ($tpl in "100","120","1100") {
foreach ($s in @(@{ Cid = $barId; Loc = $bar; Props = '{"surface":"commandBar"}'; Name = "FormActivatorCommandSet-$tpl" },
@{ Cid = $ctxId; Loc = $ctx; Props = '{}'; Name = "FormListActionsContextMenu-$tpl" })) {
if ($existing | Where-Object { $_["TenantWideExtensionComponentId"] -eq $s.Cid -and $_["TenantWideExtensionLocation"] -eq $s.Loc -and "$($_["TenantWideExtensionListTemplate"])" -eq $tpl }) { continue }
Add-PnPListItem -List $list -Values @{
Title = $s.Name; TenantWideExtensionComponentId = $s.Cid; TenantWideExtensionLocation = $s.Loc
TenantWideExtensionListTemplate = $tpl; TenantWideExtensionComponentProperties = $s.Props
TenantWideExtensionSequence = 0; TenantWideExtensionDisabled = $false } | Out-Null
}
}
A configuration saved by a newer product version (higher schemaVersion) cannot be overwritten by an older version: saving is blocked. Keep this in mind if rolling back to an older package version. See Configuration from a Newer Version.
Uninstallation
- Deactivate the form on every list: use the "Custom form" dialog (Deactivate) or
Manage-FormCustomizer.ps1 -Action Disable. The list will revert to SharePoint's default forms. - In the App Catalog, remove the package (Remove, or delete the file from the Apps for SharePoint library).
- Optionally clean up data: delete the
SiteAssets/FormCustomizerfolder on sites where the form was used, and remove the Storage Entities:Remove-PnPStorageEntity -Key SpfxFormCustomizer_License Remove-PnPStorageEntity -Key FormCraft365_ScriptsPolicy Remove-PnPStorageEntity -Key SpfxFormCustomizer_ScriptPolicy
If you remove the package before deactivating the forms, lists will continue referencing a non-existent component. Always deactivate first.