logo
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:

  1. The component ID (from the manifest)
  2. 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

MethodBest forWho controls it
serve.jsonDevelopment and testingDeveloper
ClientSideComponentProperties (PowerShell)Infrastructure config β€” URLs, flags, IDsIT admin / DevOps
ClientSideComponentProperties (Site Script)Automated provisioningIT admin
SharePoint listContent config β€” messages, labels, togglesSite owner / content editor

βœ… Summary

  • Declare a typed interface for this.properties as the generic type parameter of the extension base class.
  • Always provide safe defaults β€” ClientSideComponentProperties is admin-controlled and may be incomplete or stale.
  • Use serve.json properties blocks during development to simulate different property configurations without redeploying.
  • Set and update properties in production via Add-PnPCustomAction and Set-PnPCustomAction PowerShell commands.
  • For properties that content editors need to change, load from a SharePoint list in onInit rather than requiring an IT admin to update the UserCustomAction.

Happy coding!

Ad image