Events
Microsoft 365 Community Conference
May 6, 2 PM - May 9, 12 AM
Skill up for the era of AI at the ultimate community-led Microsoft 365 event, May 6-8 in Las Vegas.
Learn moreThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Application Customizers provide access to well-known locations on SharePoint pages that you can modify based on your business and functional requirements. For example, you can create dynamic header and footer experiences that render across all the pages in SharePoint Online.
This model is similar to using a UserCustomAction collection in a Site or Web object to modify the page experience via custom JavaScript. The key difference to SharePoint Framework (SPFx) Extensions is that your page elements won't change if changes are made to the HTML/DOM structure in SharePoint Online.
This article describes how to extend your Hello World extension to take advantage of page placeholders.
You can also follow these steps by watching the video on the Microsoft 365 Platform Communtiy (PnP) YouTube Channel:
Application Customizer extensions are supported with Site, Web, and List scopes. You can control the scope by deciding where or how the Application Customizer is registered in your SharePoint tenant.
Note
Feature XML based registration of Application Customizer is only supported with web or list level. You can however activate Application Customizer more widely either using tenant wide deployment of extensions capability or by associating Application Customizer to the UserCustomAction
collection on the Site
object.
When the Application Customizer exists in the scope and is being rendered, you can use the following method to get access to the placeholder.
// Handling the Bottom placeholder
if (!this._bottomPlaceholder) {
this._bottomPlaceholder =
this.context.placeholderProvider.tryCreateContent(
PlaceholderName.Bottom,
{ onDispose: this._onDispose });
...
}
After you get the placeholder object, you have full control over what is presented to the end user.
Notice that you're requesting a well-known placeholder by using the corresponding well-known identifier. In this case, the code is accessing the footer section of the page by using the Bottom
option on the PlaceholderName
enum.
Install the @microsoft/sp-office-ui-fabric-core package to enable importing from SPFabricCore.scss. We'll use this for defining rendering styles for our place holders.
npm install @microsoft/sp-office-ui-fabric-core
Create a new file named ./src/extensions/helloWorld/AppCustomizer.module.scss.
Update AppCustomizer.module.scss as follows:
Note
These are the styles that are used in the HTML output for the header and footer placeholders.
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.app {
.top {
height:60px;
text-align:center;
line-height:2.5;
font-weight:bold;
display: flex;
align-items: center;
justify-content: center;
background-color: $ms-color-themePrimary;
color: $ms-color-white;
}
.bottom {
height:40px;
text-align:center;
line-height:2.5;
font-weight:bold;
display: flex;
align-items: center;
justify-content: center;
background-color: $ms-color-themePrimary;
color: $ms-color-white;
}
}
In Visual Studio Code (or your preferred IDE), open ./src/extensions/helloWorld/HelloWorldApplicationCustomizer.ts.
Add the PlaceholderContent
and PlaceholderName
to the import statement from @microsoft/sp-application-base by updating the import statement as follows:
import {
BaseApplicationCustomizer,
PlaceholderContent,
PlaceholderName
} from '@microsoft/sp-application-base';
Also add the following import statements after the strings
import at the top of the file:
import styles from './AppCustomizer.module.scss';
import { escape } from '@microsoft/sp-lodash-subset';
You'll use escape
function to escape Application Customizer properties. You'll create style definitions for the output in the following steps.
Note
After pasting in the code snippet above you might be presented with an error if you use Visual Studio Code. These errors will disappear after you build the solution when the scss file is compiled into a class.
In the HelloWorldApplicationCustomizer.ts file, update the IHelloWorldApplicationCustomizerProperties
interface to add specific properties for Header and Footer, as follows:
Note
If your Application Customizer uses the ClientSideComponentProperties
JSON input, it is deserialized into the BaseExtension.properties
object. You can define an interface to describe it.
export interface IHelloWorldApplicationCustomizerProperties {
Top: string;
Bottom: string;
}
Add the following private variables inside the HelloWorldApplicationCustomizer
class. In this scenario, these can just be local variables in an onRender()
method, but if you want to share them with other objects, define them as private variables.
export default class HelloWorldApplicationCustomizer
extends BaseApplicationCustomizer<IHelloWorldApplicationCustomizerProperties> {
// These have been added
private _topPlaceholder: PlaceholderContent | undefined;
private _bottomPlaceholder: PlaceholderContent | undefined;
// ...
}
Update the onInit()
method code as follows:
public onInit(): Promise<void> {
Log.info(LOG_SOURCE, `Initialized ${strings.Title}`);
// Wait for the placeholders to be created (or handle them being changed) and then
// render.
this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);
return Promise.resolve();
}
Create a new _renderPlaceHolders()
private method with the following code:
private _renderPlaceHolders(): void {
console.log("HelloWorldApplicationCustomizer._renderPlaceHolders()");
console.log(
"Available placeholders: ",
this.context.placeholderProvider.placeholderNames
.map(name => PlaceholderName[name])
.join(", ")
);
// Handling the top placeholder
if (!this._topPlaceholder) {
this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(
PlaceholderName.Top,
{ onDispose: this._onDispose }
);
// The extension should not assume that the expected placeholder is available.
if (!this._topPlaceholder) {
console.error("The expected placeholder (Top) was not found.");
return;
}
if (this.properties) {
let topString: string = this.properties.Top;
if (!topString) {
topString = "(Top property was not defined.)";
}
if (this._topPlaceholder.domElement) {
this._topPlaceholder.domElement.innerHTML = `
<div class="${styles.app}">
<div class="${styles.top}">
<i class="ms-Icon ms-Icon--Info" aria-hidden="true"></i> ${escape(
topString
)}
</div>
</div>`;
}
}
}
// Handling the bottom placeholder
if (!this._bottomPlaceholder) {
this._bottomPlaceholder = this.context.placeholderProvider.tryCreateContent(
PlaceholderName.Bottom,
{ onDispose: this._onDispose }
);
// The extension should not assume that the expected placeholder is available.
if (!this._bottomPlaceholder) {
console.error("The expected placeholder (Bottom) was not found.");
return;
}
if (this.properties) {
let bottomString: string = this.properties.Bottom;
if (!bottomString) {
bottomString = "(Bottom property was not defined.)";
}
if (this._bottomPlaceholder.domElement) {
this._bottomPlaceholder.domElement.innerHTML = `
<div class="${styles.app}">
<div class="${styles.bottom}">
<i class="ms-Icon ms-Icon--Info" aria-hidden="true"></i> ${escape(
bottomString
)}
</div>
</div>`;
}
}
}
}
Note the following about this code:
this.context.placeholderProvider.tryCreateContent
to get access to the placeholder.Top
and Bottom
. If the properties exist, they render inside the placeholders.styles
variable is found in the code, the style sheet won't get added to the page. This is because unused references will get removed during build process.Add the following method after the _renderPlaceHolders()
method. In this case, you simply output a console message when the extension is removed from the page.
private _onDispose(): void {
console.log('[HelloWorldApplicationCustomizer._onDispose] Disposed custom top and bottom placeholders.');
}
You're now ready to test your code in SharePoint Online.
In the ./config/serve.json file, update properties section in the file to have Top and Bottom messages.
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json",
"port": 4321,
"https": true,
"serveConfigurations": {
"default": {
"pageUrl": "https://sppnp.sharepoint.com/sites/mySite/SitePages/myPage.aspx",
"customActions": {
"54328ea6-0591-4fbd-aadb-5dc51fd53235": {
"location": "ClientSideExtension.ApplicationCustomizer",
"properties": {
"Top": "Top area of the page",
"Bottom": "Bottom area of the page"
}
}
}
},
"helloWorld": {
"pageUrl": "https://sppnp.sharepoint.com/sites/mySite/SitePages/myPage.aspx",
"customActions": {
"54328ea6-0591-4fbd-aadb-5dc51fd53235": {
"location": "ClientSideExtension.ApplicationCustomizer",
"properties": {
"Top": "Top area of the page",
"Bottom": "Bottom area of the page"
}
}
}
}
}
}
Note
The GUID in the above JSON excerpt is the unique ID of the SPFx extension component. This is defined in the component's manifest. The GUID in your solution will be different as every component ID is unique.
Switch to the console window that is running gulp serve and check for errors. Gulp reports any errors in the console; you'll need to fix them before you continue. If you have the solution already running, restart it, so that we get the updated settings applied from the serve.json file.
gulp serve
Select Load debug scripts to continue loading scripts from your local host.
You should now see the custom header and footer content in your page.
Congratulations, you built your own custom header and footer using the Application Customizer!
To continue building out your extension, see Deploy your extension to SharePoint (Hello World part 3). You'll learn how to deploy and preview the Hello World extension in a SharePoint site collection without using Debug query parameters.
Events
Microsoft 365 Community Conference
May 6, 2 PM - May 9, 12 AM
Skill up for the era of AI at the ultimate community-led Microsoft 365 event, May 6-8 in Las Vegas.
Learn moreTraining
Module
Extend Microsoft Viva Connections with application customizers - Training
Learn how to extend Viva Connections with custom application customizers by using your existing web development skills. You'll learn about the scenarios where they're most suitable and how to build them.
Certification
Microsoft Certified: Power Platform Developer Associate - Certifications
Demonstrate how to simplify, automate, and transform business tasks and processes using Microsoft Power Platform Developer.