- Published on
Passing Properties Between SPFx Extension and SharePoint
Web parts have a property pane β a built-in UI for per-instance configuration that editors use directly in the browser.
SPFx extensions have no such UI. Their configuration comes from ClientSideComponentProperties, a JSON string attached to the UserCustomAction that registers the extension on a site.
Understanding how to declare, type, read, and update these properties is essential for building configurable, reusable extensions.
πΊοΈ How Extension Properties Work
When you register an SPFx extension on a site, you provide:
- The component ID (from the manifest)
- A JSON string of configuration properties (
ClientSideComponentProperties)
SharePoint passes this JSON to the extension at runtime via this.properties. The framework deserialises the JSON and makes each key available as a typed property β provided you declare the interface correctly.
This is the same mechanism across all three extension types: Application Customizer, Field Customizer, and Command Set.
π§© Step 1 β Declare the Properties Interface
Define an interface for your extension's properties. This is the generic type parameter you pass to the base class:
// For an Application Customizer
export interface ISiteHeaderProperties {
logoUrl: string;
supportEmail: string;
showEnvironmentBadge: boolean;
environment: 'dev' | 'staging' | 'production';
}
export default class SiteHeaderApplicationCustomizer
extends BaseApplicationCustomizer<ISiteHeaderProperties> {
public onInit(): Promise<void> {
// All properties are available as this.properties.<key>
console.log('Logo URL:', this.properties.logoUrl);
console.log('Environment:', this.properties.environment);
console.log('Show badge:', this.properties.showEnvironmentBadge);
return Promise.resolve();
}
}
The same pattern applies to field customizers and command sets β just change the base class:
// Field Customizer
export default class StatusBadgeFieldCustomizer
extends BaseFieldCustomizer<IStatusBadgeProperties> { ... }
// Command Set
export default class BulkEditCommandSet
extends BaseListViewCommandSet<IBulkEditProperties> { ... }
π‘οΈ Step 2 β Validate and Default Properties
ClientSideComponentProperties is controlled by whoever registers the extension β an admin running PowerShell, a site script, or a provisioning tool. There is no guarantee all properties will be present or correctly typed. Always validate and provide defaults:
public onInit(): Promise<void> {
// Provide safe defaults for any missing properties
const props = this.properties;
const logoUrl = props.logoUrl ?? '/sites/intranet/SiteAssets/logo.png';
const supportEmail = props.supportEmail ?? 'support@contoso.com';
const showBadge = props.showEnvironmentBadge ?? false;
const env = props.environment ?? 'production';
// Use the validated values β not this.properties directly
this._renderHeader({ logoUrl, supportEmail, showBadge, env });
return Promise.resolve();
}
This is especially important when the same extension is registered across multiple sites with different property sets β older registrations may not have newer properties that you added in a later version.
βοΈ Step 3 β Configure Properties in serve.json (Development)
During development, set properties in config/serve.json under the customActions or fieldCustomizers block:
{
"serveConfigurations": {
"default": {
"pageUrl": "https://contoso.sharepoint.com/sites/dev/_layouts/15/workbench.aspx",
"customActions": {
"your-component-id": {
"location": "ClientSideExtension.ApplicationCustomizer",
"properties": {
"logoUrl": "https://contoso.sharepoint.com/sites/dev/SiteAssets/logo.png",
"supportEmail": "dev-support@contoso.com",
"showEnvironmentBadge": true,
"environment": "dev"
}
}
}
},
"staging": {
"pageUrl": "https://contoso.sharepoint.com/sites/staging/_layouts/15/workbench.aspx",
"customActions": {
"your-component-id": {
"location": "ClientSideExtension.ApplicationCustomizer",
"properties": {
"logoUrl": "https://contoso.sharepoint.com/sites/staging/SiteAssets/logo.png",
"supportEmail": "staging-support@contoso.com",
"showEnvironmentBadge": true,
"environment": "staging"
}
}
}
}
}
}
For field customizers, the key is the internal column name, not the component ID:
{
"fieldCustomizers": {
"Status": {
"id": "your-component-id",
"properties": {
"completedColor": "#107C10",
"blockedColor": "#D13438"
}
}
}
}
π Step 4 β Set Properties in Production via PowerShell
When registering or updating an extension on a site, pass the properties as a JSON string:
# Register a new application customizer with properties
Add-PnPCustomAction `
-Title "SiteHeader" `
-Name "SiteHeader" `
-Location "ClientSideExtension.ApplicationCustomizer" `
-ClientSideComponentId "your-component-id" `
-ClientSideComponentProperties '{
"logoUrl": "https://contoso.sharepoint.com/sites/intranet/SiteAssets/logo.png",
"supportEmail": "support@contoso.com",
"showEnvironmentBadge": false,
"environment": "production"
}' `
-Scope Site
Update properties on an existing registration:
# Get the existing custom action and update its properties
$action = Get-PnPCustomAction -Scope Site | Where-Object { $_.Name -eq "SiteHeader" }
Set-PnPCustomAction `
-Identity $action.Id `
-ClientSideComponentProperties '{
"logoUrl": "https://contoso.sharepoint.com/sites/intranet/SiteAssets/logo-v2.png",
"supportEmail": "support@contoso.com",
"showEnvironmentBadge": false,
"environment": "production"
}' `
-Scope Site
π Step 5 β Dynamic Properties via SharePoint List
For properties that non-developers need to update (a notification message, a banner colour, a toggle), store them in a SharePoint list and fetch them in onInit instead of hardcoding in ClientSideComponentProperties:
public async onInit(): Promise<void> {
await super.onInit();
const sp = spfi().using(SPFx(this.context));
// Load config from a list β editors can update without touching PowerShell
const configItems = await sp.web.lists
.getByTitle('ExtensionConfig')
.items
.filter(`ExtensionName eq 'SiteHeader'`)
.select('Key', 'Value')();
const config = configItems.reduce((acc, item) => {
acc[item.Key] = item.Value;
return acc;
}, {} as Record<string, string>);
// Merge list-driven config with static ClientSideComponentProperties
const logoUrl = config['logoUrl'] ?? this.properties.logoUrl ?? '';
const notificationMessage = config['notificationMessage'] ?? '';
this._renderHeader({ logoUrl, notificationMessage });
return Promise.resolve();
}
This pattern gives you the best of both worlds: static infrastructure properties in ClientSideComponentProperties (managed by IT), and content properties in a SharePoint list (manageable by site owners).
π Properties Configuration Options β Summary
| Method | Best for | Who controls it |
|---|---|---|
serve.json | Development and testing | Developer |
ClientSideComponentProperties (PowerShell) | Infrastructure config β URLs, flags, IDs | IT admin / DevOps |
ClientSideComponentProperties (Site Script) | Automated provisioning | IT admin |
| SharePoint list | Content config β messages, labels, toggles | Site owner / content editor |
β Summary
- Declare a typed interface for
this.propertiesas the generic type parameter of the extension base class. - Always provide safe defaults β
ClientSideComponentPropertiesis admin-controlled and may be incomplete or stale. - Use
serve.jsonpropertiesblocks during development to simulate different property configurations without redeploying. - Set and update properties in production via
Add-PnPCustomActionandSet-PnPCustomActionPowerShell commands. - For properties that content editors need to change, load from a SharePoint list in
onInitrather than requiring an IT admin to update the UserCustomAction.
Happy coding!
Author
Ravichandran@Hi_Ravichandran
