Skip to content

Commit 36df11d

Browse files
authored
Merge pull request #1820 from saiiiiiii/GTM
2 parents 618bc7f + c638f9a commit 36df11d

24 files changed

Lines changed: 876 additions & 0 deletions

samples/js-application-gtm-injector/.eslintrc.js

Lines changed: 321 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
6+
# Dependency directories
7+
node_modules
8+
9+
# Build generated files
10+
dist
11+
lib
12+
lib-dts
13+
lib-commonjs
14+
lib-esm
15+
jest-output
16+
release
17+
solution
18+
temp
19+
*.sppkg
20+
.heft
21+
22+
# Coverage directory used by tools like istanbul
23+
coverage
24+
25+
# OSX
26+
.DS_Store
27+
28+
# Visual Studio files
29+
.ntvs_analysis.dat
30+
.vs
31+
bin
32+
obj
33+
34+
# Resx Generated Code
35+
*.resx.ts
36+
37+
# Styles Generated Code
38+
*.scss.ts
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Ignore everything by default
2+
**
3+
4+
# Use negative patterns to bring back the specific things we want to publish
5+
!/bin/**
6+
!/dist/**
7+
!/EULA/**
8+
!/lib/**
9+
!ThirdPartyNotice.txt
10+
11+
# Ignore certain files in the above folders
12+
/dist/*.stats.*
13+
/dist/**/*.js.map
14+
/lib/**/*.js.map
15+
/lib/**/test/**
16+
17+
# NOTE: These don't need to be specified, because NPM includes them automatically.
18+
#
19+
# package.json
20+
# README (and its variants)
21+
# CHANGELOG (and its variants)
22+
# LICENSE / LICENCE
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v23.0.0
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"@microsoft/generator-sharepoint": {
3+
"useGulp": false,
4+
"plusBeta": false,
5+
"isCreatingSolution": true,
6+
"nodeVersion": "22.22.0",
7+
"sdksVersions": {},
8+
"version": "1.22.2",
9+
"libraryName": "gtm-tracker",
10+
"libraryId": "1bebcb20-2e53-41a2-9f5c-cba42eec94fe",
11+
"environment": "spo",
12+
"packageManager": "npm",
13+
"solutionName": "gtm-tracker",
14+
"solutionShortDescription": "gtm-tracker description",
15+
"skipFeatureDeployment": true,
16+
"isDomainIsolated": false,
17+
"componentType": "extension",
18+
"extensionType": "ApplicationCustomizer"
19+
}
20+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Google Tag Manager Injector
2+
3+
## Summary
4+
5+
This SharePoint Framework (SPFx) Application Customizer injects **Google Tag Manager (GTM)** into SharePoint Modern pages, enabling analytics and tag management across your tenant. It initializes the standard GTM `dataLayer`, loads the GTM script via `SPComponentLoader`, and hooks into SharePoint's navigation events to track **Single Page Application (SPA) route changes** — ensuring every page view is captured without a full browser reload.
6+
7+
Built to be compliant with the **SharePoint Online Content Security Policy (CSP)** enforcement introduced in March 2026. Because GTM is loaded dynamically at runtime, `https://www.googletagmanager.com` must be manually registered as a **Trusted Script Source** in the SharePoint Admin Center before deployment. See [Prerequisites](#prerequisites) for the required steps.
8+
9+
![GTM Injector in action](./assets/gtm-injector-demo.png)
10+
11+
## Compatibility
12+
13+
| :warning: Important |
14+
|:---------------------------|
15+
| Every SPFx version is optimally compatible with specific versions of Node.js. In order to be able to Toolchain this sample, you need to ensure that the version of Node on your workstation matches one of the versions listed in this section. This sample will not work on a different version of Node.|
16+
|Refer to <https://aka.ms/spfx-matrix> for more information on SPFx compatibility. |
17+
18+
This sample is optimally compatible with the following environment configuration:
19+
20+
![SPFx 1.22.2](https://img.shields.io/badge/SPFx-1.22.2-green.svg)
21+
![Node.js v22](https://img.shields.io/badge/Node.js-v22-green.svg)
22+
![Toolchain: Heft](https://img.shields.io/badge/Toolchain-Heft-green.svg)
23+
![Compatible with SharePoint Online](https://img.shields.io/badge/SharePoint%20Online-Compatible-green.svg)
24+
![Does not work with SharePoint 2019](https://img.shields.io/badge/SharePoint%20Server%202019-Incompatible-red.svg "SharePoint Server 2019 requires SPFx 1.4.1 or lower")
25+
![Does not work with SharePoint 2016 (Feature Pack 2)](https://img.shields.io/badge/SharePoint%20Server%202016%20(Feature%20Pack%202)-Incompatible-red.svg "SharePoint Server 2016 Feature Pack 2 requires SPFx 1.1")
26+
![Local Workbench Unsupported](https://img.shields.io/badge/Local%20Workbench-Unsupported-red.svg "Local workbench is no longer available as of SPFx 1.13 and above")
27+
![Hosted Workbench Compatible](https://img.shields.io/badge/Hosted%20Workbench-Compatible-green.svg)
28+
![Compatible with Remote Containers](https://img.shields.io/badge/Remote%20Containers-Compatible-green.svg)
29+
30+
## Applies to
31+
32+
- [SharePoint Framework](https://aka.ms/spfx)
33+
- [Microsoft 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-developer-tenant)
34+
35+
> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram)
36+
37+
## Prerequisites
38+
39+
- Node.js v22.14.0 or compatible version
40+
- A Google Tag Manager account and Container ID (e.g. `GTM-XXXXXXX`)
41+
- Tenant Administrator access to the SharePoint Admin Center
42+
43+
### ⚠️ Required: CSP Trusted Script Source Registration
44+
45+
SharePoint Online enforces [Content Security Policy (CSP)](https://learn.microsoft.com/sharepoint/dev/spfx/content-securty-policy-trusted-script-sources) on all Modern pages from **March 1, 2026** onwards. This solution uses `SPComponentLoader.loadScript()` to load GTM dynamically at runtime — SharePoint does **not** auto-trust sources loaded this way, so you must register the GTM domain manually.
46+
47+
**Via SharePoint Admin Center:**
48+
49+
1. Go to **SharePoint Admin Center****Advanced****Script sources**
50+
2. Select **Add source**
51+
3. Enter `https://www.googletagmanager.com` and save
52+
53+
> Wildcard expressions (e.g. `*.googletagmanager.com`) are not supported. Use the exact origin.
54+
55+
**Verify:** Load a Modern page with the solution active and append `?csp=enforce` to the URL. Open DevTools (F12) → Console. No `Content Security Policy` violations means the configuration is correct.
56+
57+
58+
## Contributors
59+
60+
- [@saiiiiiii](https://github.com/saiiiiiii) |
61+
62+
## Version history
63+
64+
| Version | Date | Comments |
65+
| ------- | ------------- | --------------- |
66+
| 1.0 | April 2026 | Initial release |
67+
68+
## Disclaimer
69+
70+
**THIS CODE IS PROVIDED _AS IS_ WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
71+
72+
---
73+
74+
## Minimal Path to Awesome
75+
76+
- Clone this repository
77+
- Update your GTM Container ID in `src/extensions/gtmInjector/GtmInjectorApplicationCustomizer.ts`:
78+
79+
```typescript
80+
const GTM_ID: string = 'GTM-XXXXXXX'; // Replace with your GTM Container ID
81+
```
82+
83+
- Ensure that you are at the solution folder
84+
- In the command-line run:
85+
- `npm install -g @rushstack/heft`
86+
- `npm install`
87+
- `heft start`
88+
- Add your SharePoint site URL to `config/serve.json` under `pageUrl`
89+
- Complete the [CSP Trusted Script Source registration](#-required-csp-trusted-script-source-registration) before testing against a CSP-enforced tenant
90+
91+
**To build and package for deployment:**
92+
93+
```bash
94+
heft test --clean --production
95+
heft package-solution --production
96+
```
97+
98+
Upload the generated `.sppkg` from `sharepoint/solution/gtm-tracker.sppkg` to your Tenant App Catalog. The solution is configured with `skipFeatureDeployment: true`, allowing tenant-wide activation directly from the App Catalog.
99+
100+
Other build commands can be listed using `heft --help`.
101+
102+
## Features
103+
104+
This extension injects Google Tag Manager into SharePoint Modern pages and enables full analytics tracking across Single Page Application (SPA) navigations.
105+
106+
This extension illustrates the following concepts:
107+
108+
- Dynamically loading an external analytics script using `SPComponentLoader.loadScript()` in a CSP-compliant manner — avoiding inline scripts which are blocked by SharePoint Online from March 2026 onwards
109+
- Initializing a standard GTM `dataLayer` and pushing the `gtm.js` start event on page load
110+
- Tracking SPA navigations by listening to both SharePoint's `navigatedEvent` (from `this.context.application`) and the browser's native `popstate` event
111+
- Preventing duplicate script injection across component re-renders using a `window.__gtmLoaded` guard flag
112+
- Deploying a tenant-wide Application Customizer with `skipFeatureDeployment: true` for zero-touch rollout
113+
114+
> Notice that better pictures and documentation will increase the sample usage and the value you are providing for others. Thanks for your submissions in advance.
115+
116+
> Share your web part with others through Microsoft 365 Patterns and Practices program to get visibility and exposure. More details on the community, open-source projects and other activities from http://aka.ms/m365pnp.
117+
118+
## References
119+
120+
- [Getting started with SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-developer-tenant)
121+
- [Support for Content Security Policy (CSP) in SharePoint Online](https://learn.microsoft.com/sharepoint/dev/spfx/content-securty-policy-trusted-script-sources)
122+
- [SharePoint Online CSP Enforcement Dates and Guidance](https://techcommunity.microsoft.com/blog/spblog/sharepoint-online-content-security-policy-csp-enforcement-dates-and-guidance/4472662)
123+
- [Use Microsoft Graph in your solution](https://learn.microsoft.com/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis)
124+
- [Publish SharePoint Framework applications to the Marketplace](https://learn.microsoft.com/sharepoint/dev/spfx/publish-to-marketplace-overview)
125+
- [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development
126+
- [Google Tag Manager — Developer Quickstart](https://developers.google.com/tag-platform/tag-manager/web)
127+
- [Heft Documentation](https://heft.rushstack.io/)
128+
129+
130+
## Help
131+
132+
If you encounter any issues while using this sample, [create a new issue](https://github.com/saiiiiiii/sp-dev-fx-extensions/samples/js-application-gtm-injector/issues/new).
133+
134+
<img src="https://m365-visitor-stats.azurewebsites.net/sp-dev-fx-extensions/samples/js-application-gtm-injector" />
16.9 KB
Loading
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
[
2+
{
3+
"name": "pnp-sp-dev-spfx-extensions-js-application-gtm-injector",
4+
"source": "pnp",
5+
"title": "Google Tag Manager Injector",
6+
"shortDescription": "This SharePoint Framework (SPFx) Application Customizer injects Google Tag Manager (GTM) into SharePoint Modern pages, enabling analytics and tag management across your tenant. It initializes the standard GTM dataLayer, loads the GTM script via SPComponentLoader, and hooks into SharePoint's navigation events to track Single Page Application (SPA) route changes — ensuring every page view is captured without a full browser reload.",
7+
"url": "https://github.com/pnp/sp-dev-fx-extensions/tree/main/samples/js-application-gtm-injector",
8+
"longDescription": [
9+
"This SharePoint Framework (SPFx) Application Customizer injects Google Tag Manager (GTM) into SharePoint Modern pages, enabling analytics and tag management across your tenant. It initializes the standard GTM dataLayer, loads the GTM script via SPComponentLoader, and hooks into SharePoint's navigation events to track Single Page Application (SPA) route changes — ensuring every page view is captured without a full browser reload."
10+
],
11+
"creationDateTime": "2026-04-01",
12+
"updateDateTime": "2026-04-01",
13+
"products": [
14+
"SharePoint"
15+
],
16+
"metadata": [
17+
{
18+
"key": "CLIENT-SIDE-DEV",
19+
"value": "JavaScript"
20+
},
21+
{
22+
"key": "SPFX-VERSION",
23+
"value": "1.22.2"
24+
}
25+
],
26+
"tags": [],
27+
"categories": [
28+
"SPFX-APPLICATION-EXTENSION"
29+
],
30+
"thumbnails": [
31+
{
32+
"name": "gtm-injector-demo.png",
33+
"type": "image",
34+
"order": 100,
35+
"url": "https://github.com/pnp/sp-dev-fx-extensions/raw/main/samples/js-application-gtm-injector/assets/gtm-injector-demo.png",
36+
"alt": "Web Part Preview"
37+
}
38+
],
39+
"authors": [
40+
{
41+
"gitHubAccount": "saiiiiiii",
42+
"pictureUrl": "https://github.com/saiiiiiii.png",
43+
"name": "@saiiiiiii"
44+
}
45+
],
46+
"references": [
47+
{
48+
"name": "Overview of SharePoint Framework Extensions",
49+
"description": "You can use SharePoint Framework (SPFx) Extensions to extend the SharePoint user experience. With SPFx Extensions, you can customize more facets of the SharePoint experience, including notification areas, toolbars, and list data views. SPFx Extensions are available in all Microsoft 365 subscriptions for production usage.",
50+
"url": "https://learn.microsoft.com/sharepoint/dev/spfx/extensions/overview-extensions?WT.mc_id=m365-15741-cxa"
51+
},
52+
{
53+
"name": "Use page placeholders from Application Customizer",
54+
"description": "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.",
55+
"url": "https://learn.microsoft.com/sharepoint/dev/spfx/extensions/get-started/using-page-placeholder-with-extensions?WT.mc_id=m365-15741-cxa"
56+
}
57+
]
58+
}
59+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
3+
"version": "2.0",
4+
"bundles": {
5+
"gtm-injector-application-customizer": {
6+
"components": [
7+
{
8+
"entrypoint": "./lib/extensions/gtmInjector/GtmInjectorApplicationCustomizer.js",
9+
"manifest": "./src/extensions/gtmInjector/GtmInjectorApplicationCustomizer.manifest.json"
10+
}
11+
]
12+
}
13+
},
14+
"externals": {},
15+
"localizedResources": {
16+
"GtmInjectorApplicationCustomizerStrings": "lib/extensions/gtmInjector/loc/{locale}.js"
17+
}
18+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
3+
"workingDir": "./release/assets/",
4+
"account": "<!-- STORAGE ACCOUNT NAME -->",
5+
"container": "gtm-tracker",
6+
"accessKey": "<!-- ACCESS KEY -->"
7+
}

0 commit comments

Comments
 (0)