Extensions: Code That Wraps the Whole Site
Lesson 6: Extensions โ Code That Wraps the Whole Site
Web parts live inside pages. SPFx extensions live around them: code that runs on every page of a site, or that changes how lists behave. If you need a global banner, a custom column, or a toolbar button, an extension is the tool โ and it ships in the exact same .sppkg pipeline as web parts.
The three extension types
| Type | Use case | Scope once installed |
|---|---|---|
| Application customizer | Inject HTML/scripts into the header or footer placeholders of every modern page โ banners, navigation tweaks, analytics tags. | Runs automatically across the site collection where the app is installed. |
| Field customizer | Render a column's cells with custom UI (progress bars, status pills, images) instead of plain text. | Wired to specific list columns via the column's client-side component settings / provisioning. |
| List view command set | Add custom buttons to the list toolbar and row context menu โ "Approve", "Export", custom workflows. | Attached to specific list views when you configure the command set. |
In other words: application customizers are site-wide by nature; field customizers and command sets are opt-in per list and are usually attached during provisioning (which is why Lesson 2's tools earn their keep).
A real application customizer
Scaffold one with the generator (yo @microsoft/sharepoint โ Application Customizer) and you get a class like this โ here extended to show a dismissible notice bar in the top placeholder:
import { Log } from '@microsoft/sp-core-library';
import {
BaseApplicationCustomizer,
PlaceholderName
} from '@microsoft/sp-application-base';
export interface INoticeBarApplicationCustomizerProperties {
message: string; // configurable: set when you register the extension
}
export default class NoticeBarApplicationCustomizer
extends BaseApplicationCustomizer<INoticeBarApplicationCustomizerProperties> {
public onInit(): Promise<void> {
Log.info('NoticeBar', 'initialized');
// Placeholders appear async โ render when the top region is ready
this.context.placeholderProvider.changedEvent.add(this, this._render);
this._render();
return Promise.resolve();
}
private _render(): void {
const placeholder = this.context.placeholderProvider
.tryCreateContent(PlaceholderName.Top, { onDispose: null });
if (!placeholder) { return; } // top slot already taken by another extension
const message = this.properties.message || 'Default notice';
placeholder.domElement.innerHTML = `
<div style="background:#fde7e9;color:#a4262c;padding:8px 16px;
font-family:Segoe UI,sans-serif;font-size:14px;text-align:center">
${message}
</div>`;
}
}
What to notice
onInit()is the extension lifecycle hook โ the right place to subscribe and render once.placeholderProviderexposes the page's injection slots (PlaceholderName.Top/.Bottom). Only one extension can own a slot;tryCreateContentreturnsundefinedif it's taken โ always handle that.- Properties travel with registration: unlike web parts, an app customizer's
messageisn't set by a property pane โ you provide it when registering/installing the extension (via the component's properties during provisioning). The code must default gracefully.
๐ง Knowledge Check
1. Which extension type runs automatically across a site collection once its app is installed?
2. What does tryCreateContent(PlaceholderName.Top) return if another extension already owns the top placeholder?
3. A field customizer and a command set both need what after the app is installed?