← Back to Blog

SPFx Application Customizer: Build Global Headers and Footers for SharePoint Online (2026)

Add a branded global header and footer to every modern SharePoint page using an SPFx Application Customizer extension — with tenant-wide deployment via PnP PowerShell.

SPFx Application Customizer: Build Global Headers and Footers for SharePoint Online (2026)


The Problem SharePoint Add-in Retirement Created

On April 2, 2026, Microsoft officially retired SharePoint Add-ins. Every SharePoint Add-in stopped working across all tenants — no extensions, no exceptions. (Microsoft Learn)

If your organization had a custom branded header or footer deployed via an Add-in, it stopped rendering that day. The only supported replacement is a SharePoint Framework Application Customizer extension — and if you have not migrated yet, this guide will walk you through building one from scratch in 2026.

Even if you were never on Add-ins, Application Customizers are the right tool whenever you need something to appear on every page across your SharePoint tenant: a cookie consent banner, a global navigation bar, an emergency announcement strip, or a branded footer with links and contact information.

---

Key Takeaways

  • Application Customizers are SPFx extensions that inject HTML into two page-level placeholders: PageHeader and PageFooter.

  • They run on every modern SharePoint page — site pages, list views, document libraries — without any per-page configuration.

  • Tenant-wide deployment via the Tenant Wide Extensions list pushes your customizer to all sites automatically, without requiring site owners to install anything.

  • The April 2026 SharePoint Add-in retirement deadline means any organization still relying on Add-in-based branding needs to migrate now.

  • In SPFx 1.22+, new extension projects use the Heft build system instead of Gulp. The scaffolding and build commands changed.

  • Field Customizers are being retired on June 30, 2026 — but Application Customizers and Command Sets are unaffected and fully supported.

---

What Is an SPFx Application Customizer?

The SharePoint Framework offers four types of extensions that run inside the SharePoint page itself, outside the boundaries of a web part zone:

Extension TypePurposeStatus
Application CustomizerPage-level scripts and HTML via placeholdersSupported
Command SetButtons in list/library toolbars and context menusSupported
Form CustomizerOverride new/edit/view forms in listsSupported
Field CustomizerCustom column rendering in list viewsRetiring June 30, 2026

Application Customizers sit at the top of this list because they have the broadest surface area: your code runs on every page load across every site in the tenant.

The two available placeholders are:

  • PageHeader — the strip at the very top of every SharePoint modern page, above the suite bar

  • PageFooter — the strip at the bottom of every page, below page content

You get a

node to render whatever HTML you want into both placeholders. React, plain HTML, or a third-party framework — all valid.

---

SPFx Application Customizer in Practice: What Organizations Build

Tens of millions of users interact with custom SPFx solutions on a daily basis in Microsoft 365. (Microsoft 365 Developer Blog) Application Customizers are among the most commonly deployed extension types because the use cases are universal:

Header use cases:

  • Global navigation bar with megamenu

  • Announcement banners ("System maintenance this weekend")

  • Logged-in user's name and profile photo

  • Breadcrumb navigation override

  • GDPR cookie consent banner (required to fire before page interaction)

Footer use cases:

  • Company branding: logo, copyright, legal links

  • Help desk contact information

  • Site-specific metadata (last modified date, site owner)

  • Accessibility statement link

Any of these would previously have been deployed as a SharePoint Add-in or a JavaScript injection via a user custom action. Both approaches are now retired. Application Customizer is the supported path forward.

---

Prerequisites

Before scaffolding, confirm your environment matches current requirements.

Required software:


  • A SharePoint Online tenant with a configured App Catalog

  • VS Code (recommended)

Check your Node version:

node --version
# Should output v22.x.x

If you are on Node 18 or 20, use nvm-windows to switch:

nvm install 22
nvm use 22

Install the SPFx toolchain (SPFx 1.23 + Heft):

npm install -g @microsoft/generator-sharepoint@1.23.0

If you have already migrated to the new SPFx CLI preview (introduced in SPFx 1.23), you can use that instead. See the Yeoman to SPFx CLI migration guide for the new workflow.

---

Scaffold the Application Customizer

With SPFx 1.22 and later, new projects use Heft instead of Gulp. The Yeoman generator still works but the build scripts have changed. See the full Gulp-to-Heft migration guide if you are upgrading an existing project rather than starting fresh.

Run the Yeoman generator:

yo @microsoft/sharepoint

When prompted:

PromptAnswer
Solution namecontoso-header-footer
Target environmentSharePoint Online only
Component typeExtension
Extension typeApplication Customizer
NameContosoHeaderFooter
DescriptionGlobal header and footer for Contoso intranet

The scaffolded structure will look like this:

contoso-header-footer/
├── config/
│ ├── package-solution.json
│ └── serve.json
├── src/
│ └── extensions/
│ └── contosoHeaderFooter/
│ ├── ContosoHeaderFooterApplicationCustomizer.manifest.json
│ ├── ContosoHeaderFooterApplicationCustomizer.ts
│ └── loc/
│ └── myStrings.d.ts
├── package.json
└── rig.json ← new in SPFx 1.22+ (Heft)

The rig.json file is new in the Heft-based toolchain — it points to @microsoft/spfx-web-build-rig and replaces the old gulpfile.js.

---

Writing the Application Customizer

Open ContosoHeaderFooterApplicationCustomizer.ts. The scaffolded class extends BaseApplicationCustomizer. You will override the onInit method to attach your header and footer.

Basic Placeholder Implementation

import { override } from '@microsoft/decorators';
import {
BaseApplicationCustomizer,
PlaceholderContent,
PlaceholderName
} from '@microsoft/sp-application-base';

export interface IContosoHeaderFooterApplicationCustomizerProperties {
headerMessage: string;
footerText: string;
}

export default class ContosoHeaderFooterApplicationCustomizer
extends BaseApplicationCustomizer<IContosoHeaderFooterApplicationCustomizerProperties> {

private _headerPlaceholder: PlaceholderContent | undefined;
private _footerPlaceholder: PlaceholderContent | undefined;

@override
public onInit(): Promise<void> {
this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceholders);
this._renderPlaceholders();
return Promise.resolve();
}

private _renderPlaceholders(): void {
// --- HEADER ---
if (!this._headerPlaceholder) {
this._headerPlaceholder =
this.context.placeholderProvider.tryCreateContent(PlaceholderName.Top);
}

if (this._headerPlaceholder) {
const message: string =
this.properties.headerMessage || 'Welcome to the Contoso Intranet';
this._headerPlaceholder.domElement.innerHTML =
<div class="contoso-header" role="banner">
<div class="contoso-header__inner">
<a class="contoso-header__logo" href="/" aria-label="Contoso Home">
<img src="/sites/intranet/SiteAssets/logo.svg" alt="Contoso" height="32" />
</a>
<span class="contoso-header__message">${this._escapeHtml(message)}</span>
</div>
</div>
;
}

// --- FOOTER ---
if (!this._footerPlaceholder) {
this._footerPlaceholder =
this.context.placeholderProvider.tryCreateContent(PlaceholderName.Bottom);
}

if (this._footerPlaceholder) {
const year = new Date().getFullYear();
const footerText: string = this.properties.footerText || 'Contoso Corporation';
this._footerPlaceholder.domElement.innerHTML =
<div class="contoso-footer" role="contentinfo">
<div class="contoso-footer__inner">
<span>&copy; ${year} ${this._escapeHtml(footerText)}. All rights reserved.</span>
<nav class="contoso-footer__links" aria-label="Footer navigation">
<a href="/sites/intranet/SitePages/Privacy.aspx">Privacy Policy</a>
<a href="/sites/intranet/SitePages/Accessibility.aspx">Accessibility</a>
<a href="https://support.contoso.com">Help Desk</a>
</nav>
</div>
</div>
;
}
}

private _escapeHtml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
}

Why changedEvent Matters

SharePoint renders placeholders lazily. On some pages, PlaceholderName.Top or PlaceholderName.Bottom may not be available when onInit first runs — they become available later during the page load cycle. Subscribing to changedEvent ensures your callback fires again whenever the placeholder provider's state changes, so your header/footer always renders even on complex page layouts.

---

Passing Properties from the Tenant Wide Extensions List

Notice the interface IContosoHeaderFooterApplicationCustomizerProperties. Properties declared here can be passed as JSON via the ClientSideComponentProperties column in the Tenant Wide Extensions list. This allows site collection administrators to customize the header message per site without changing the code.

Example JSON you would put in ClientSideComponentProperties:

{
"headerMessage": "Internal use only — Contoso employees",
"footerText": "Contoso Legal & Compliance"
}

This pattern keeps your extension generic while allowing per-tenant or per-site configuration. The same compiled .sppkg runs everywhere; only the properties differ.

---

Styling Without Violating SharePoint's CSP

Starting in January 2026, SharePoint Online enforces style-src Content Security Policy headers on all GA tenants. Inline