├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── Snippets.js ├── TagManager.js ├── __tests__ │ ├── Snippets.spec.js │ ├── TagManager.spec.js │ └── warn.spec.js ├── index.js └── utils │ └── warn.js └── types └── index.d.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react", 4 | ["@babel/preset-env", { "targets": { "node": "current" } }] 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{ts,js}] 4 | indent_style = tab 5 | indent_size = 3 6 | quote_type = single -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@babel/eslint-parser", 3 | "extends": "eslint:recommended", 4 | "env": { 5 | "es6": true, 6 | "node": true, 7 | "browser": true, 8 | "jest": true 9 | }, 10 | "parserOptions": { 11 | "ecmaFeatures": { 12 | "jsx": true 13 | } 14 | }, 15 | "rules": { 16 | "consistent-this": 0, 17 | "no-console": 0 18 | }, 19 | "overrides": [ 20 | { 21 | "files": ["tests/**/*"], 22 | "env": { 23 | "jest": true 24 | } 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | coverage 4 | .DS_Store 5 | dist -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "printWidth": 80, 5 | "semi": false, 6 | "singleQuote": true, 7 | "tabWidth": 3, 8 | "trailingComma": "all", 9 | "useTabs": true 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 HolidayCheck 4 | Modified work Copyright (c) 2017 Aline Morelli 5 | Modified work Copyright (c) 2024 Sooro GmbH 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software is furnished to do so, 12 | subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-gtm-module 2 | 3 | ### React Google Tag Manager Module 4 | 5 | This is a JS module to [React](https://facebook.github.io/react/)-based apps that implement Google Tag Manager (GTM). It is designed so that the [GTM snippet](https://developers.google.com/tag-manager/quickstart) can be injected and used with minimal effort. 6 | 7 | It was [originally created and maintained](https://github.com/alinemorelli/react-gtm) by [@alinemorelli](https://github.com/alinemorelli), but has not been further developed since September 2020. A whole series of feature requests and pull requests have remained unprocessed since then, which have been partly included here. On top a few further changes and a fix were applied. You can find out more about it in the [comparison section](#comparison-to-orginal-module). 8 | A [migration guide](#migration-guide) is provided too. 9 | 10 | ## Getting Started 11 | 12 | Open up your terminal and install the package with your preferred package manager. 13 | 14 | ```bash 15 | npm install @sooro-io/react-gtm-module 16 | # OR 17 | yarn add @sooro-io/react-gtm-module 18 | ``` 19 | 20 | You need to adjust the code in your React application's entry file. If you started your application via Create React App, it's about `src/index.js` or `src/index.ts`. 21 | 22 | ```js 23 | import React from 'react' 24 | import ReactDOM from 'react-dom/client' 25 | import './index.css' 26 | import App from './App' 27 | 28 | // start changes 29 | import TagManager from '@sooro-io/react-gtm-module' 30 | 31 | const tagManagerArgs = { 32 | gtmId: 'GTM-xxxxxx', // replace with your GTM container ID 33 | } 34 | 35 | TagManager.initialize(tagManagerArgs) 36 | // end changes 37 | 38 | const root = ReactDOM.createRoot(document.getElementById('root')) 39 | root.render( 40 | 41 | 42 | , 43 | ) 44 | ``` 45 | 46 | That's it, you have successfully added the GTM to your react application. You can start a preview session of your GTM container to confirm that everything is working. 47 | 48 | Alternatively, you can go to the console in your browser tab and enter `google_tag_manager`. You should see a few informations and functions. 49 | 50 | If you are facing problems, please check if you receive a 404 error for the GTM script. If so, no changes have been published to the GTM container yet. Once you have done this, the error will disappear and the GTM script will be injected. 51 | 52 | ## Interact with the dataLayer 53 | 54 | You can interact with the dataLayer (to trigger events or push new data to it) in your components like this: 55 | 56 | ```js 57 | import React from 'react' 58 | import TagManager from 'react-gtm-module' 59 | 60 | const Home = () => { 61 | TagManager.dataLayer({ 62 | dataLayer: { 63 | event: 'home_viewed', 64 | // add other properties to set a value 65 | // to unset a property use undefined as value 66 | }, 67 | }) 68 | 69 | return ( 70 |
71 |

Home

72 |
73 | ) 74 | } 75 | 76 | export default Home 77 | ``` 78 | 79 | If you are using multiple dataLayers you can define to which dataLayer the object is added by using the `dataLayerName` property. 80 | 81 | ```js 82 | TagManager.dataLayer({ 83 | dataLayer: { 84 | event: 'identified', 85 | userId: 'dc26b3de-5186-4fa5-a89a-60762111a5b4', 86 | }, 87 | dataLayerName: 'personalInformation', 88 | }) 89 | ``` 90 | 91 | ## Configuration 92 | 93 | To adapt your GTM configuration to your needs, a number of options are available. You can find examples for each of them below the table. 94 | 95 | | Value | Type | Required | Notes | 96 | | -------------------------------------- | ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------- | 97 | | gtmId | `String` | Yes | The ID of your GTM Container. | 98 | | [dataLayer](#datalayer) | `Object` | No | Information that should be added to the dataLayer before initialization. | 99 | | [dataLayerName](#datalayername) | `String` | No | Customize the name of the dataLayer object. | 100 | | [events](#events) | `Array of Objects` | No | Additional events which will be added to the dataLayer during initialization. | 101 | | [auth](#auth--preview-environments) | `String` | No | If you use GTM's environment function, you need to pass the gtm_auth query parameter here. | 102 | | [preview](#auth--preview-environments) | `String` | No | If you use GTM's environment function, you need to pass the gtm_preview query parameter here. | 103 | | [nonce](#nonce) | `String` | No | Set the nonce if you use a Content Security Policy. | 104 | | [source](#source) | `String` | No | Customize the GTM script URL if you serve the Google scripts through your tagging servers and/or mask your GTM script. | 105 | 106 | ### dataLayer 107 | 108 | Information that should be added to the dataLayer before initialization. The information will be added before `gtm.js` event. 109 | 110 | ```js 111 | const tagManagerArgs = { 112 | gtmId: 'GTM-xxxxxx', 113 | dataLayer: { 114 | currency: 'USD', 115 | language: 'en', 116 | }, 117 | } 118 | ``` 119 | 120 | ### dataLayerName 121 | 122 | Customize the name of the dataLayer object. 123 | 124 | ```js 125 | const tagManagerArgs = { 126 | gtmId: 'GTM-xxxxxx', 127 | dataLayerName: 'personalInformation', 128 | } 129 | ``` 130 | 131 | ### events 132 | 133 | Additional events which will be added to the dataLayer during initialization (after `gtm.js` event). 134 | 135 | ```js 136 | const tagManagerArgs = { 137 | gtmId: 'GTM-xxxxxx', 138 | events: [ 139 | { 140 | event: 'consent_loaded', 141 | consentAnalytics: true, 142 | consentAds: false, 143 | consentPreferences: true, 144 | }, 145 | ], 146 | } 147 | ``` 148 | 149 | ### auth & preview (Environments) 150 | 151 | You have to set both properties to interact with an certain environment. Environments are an advanced GTM feature. [Here is a guide which helps you to implement it](https://marketlytics.com/blog/google-tag-manager-environments/) if you are interested. 152 | 153 | You have to manually extract the necessary information from the environment snippet. Inside your GTM container go to **Admin** -> **Environments**. On this page you see a list of all your environments. On the right you have the **Actions** for each entry. Click on it and use the function **Get Snippet**. 154 | 155 | ```js 156 | const tagManagerArgs = { 157 | gtmId: 'GTM-xxxxxx', 158 | auth: '6sBOnZx1hqPcO01xPOytLK', // add here the value of gtm_auth 159 | preview: 'env-staging', // add here the value of gtm_preview 160 | } 161 | ``` 162 | 163 | Please note that `>m_cookies_win=x` will be automatically added to the GTM script URL as soon the two properties are set. [Please check the article of Simo Ahava for more details and challenges about it](https://www.simoahava.com/analytics/better-qa-with-google-tag-manager-environments/). 164 | 165 | ### nonce 166 | 167 | [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) helps to mitigate certain types of attacks. This includes especially cross-site scripting as only certain sources of scripts (domains) are allowed to be executed. [GTM supports to feature natively, you can find a whole guide in docs](https://developers.google.com/tag-platform/security/guides/csp). 168 | 169 | ```js 170 | const tagManagerArgs = { 171 | gtmId: 'GTM-xxxxxx', 172 | nonce: 'KCenr5lELncZ6JJlHmerd9aIjddJfBEZ', // from you server 173 | } 174 | ``` 175 | 176 | ### source 177 | 178 | If you use your GTM Tagging Servers to serve Google scripts, you have to use a custom URL. This property allows you to overwrite the default one `https://googletagmanager.com/gtm.js` with a custom value. 179 | 180 | For more information about this feature [read the article in GTM docs](https://developers.google.com/tag-platform/tag-manager/server-side/dependency-serving?tag=gtm). It contains also a step-by-step guide. 181 | 182 | ```js 183 | const tagManagerArgs = { 184 | gtmId: 'GTM-xxxxxx', 185 | source: 'https://gtm.example.com/gtm.js', // URL including script! 186 | } 187 | ``` 188 | 189 | ## Comparison to Orginal Module 190 | 191 | Basically it is still the JS module you know. However, there are a number of additional functions and a bug fix, which is a breaking change. 192 | 193 | **Changes and improvements:** 194 | 195 | - support for Content Security Policy to mitigate risks related to cross-site scripting 196 | - possibility to overwrite the GTM script URL in order to use certain functions of GTM tagging servers, which can lead to improved privacy 197 | - addition of TypeScript definitions into the package itself 198 | - simplified documentation for all features and various corrections and additions 199 | - improved test coverage to 100% (based on Jest) 200 | - updated depedencies and the removal of unnecessary ones 201 | 202 | **Bug fix:** 203 | The `events` argument was inteded to add events into the dataLayer before GTM gets initialized. Against the description only properties could be added. We fixed this problem and you are now able to add events. Properties can be still added by the setting the initial dataLayer (`dataLayer` arg). 204 | 205 | ## Migration Guide 206 | 207 | **Dependencies** 208 | All you have to do is change the package. The previous TypeScript definitions are no longer required as the types are now included in the package. 209 | 210 | ```bash 211 | npm uninstall react-gtm-module @types/react-gtm-module 212 | npm install @sooro-io/react-gtm-module 213 | 214 | # OR 215 | 216 | yarn remove react-gtm-module @types/react-gtm-module 217 | yarn install @sooro-io/react-gtm-module 218 | ``` 219 | 220 | **Imports** 221 | 222 | ```diff 223 | - import TagManager from 'react-gtm-module' 224 | + import TagManager from '@sooro-io/react-gtm-module' 225 | ``` 226 | 227 | **`events` arg** 228 | If you use the `events` arg in the initialization you need to switch to the `dataLayer` arg. If you want to add events, [please check the example](#events). 229 | 230 | ```diff 231 | const tagManagerArgs = { 232 | gtmId: 'GTM-xxxxxx', 233 | - events: { 234 | - currency: 'USD', 235 | - language: 'en', 236 | - }, 237 | + dataLayer: { 238 | + currency: 'USD', 239 | + language: 'en', 240 | + }, 241 | } 242 | ``` 243 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('jest').Config} */ 2 | const config = { 3 | testEnvironment: 'jsdom', 4 | testRegex: '__tests__.*\\.spec.js$', 5 | collectCoverage: true, 6 | coverageReporters: ['text'], 7 | } 8 | 9 | module.exports = config 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sooro-io/react-gtm-module", 3 | "version": "3.0.1", 4 | "description": "React Google Tag Manager Module", 5 | "main": "dist/index.js", 6 | "types": "types/index.d.ts", 7 | "scripts": { 8 | "lint": "eslint .", 9 | "jest": "jest", 10 | "test": "npm run lint && npm run jest", 11 | "build": "babel src -d dist --ignore src/__tests__", 12 | "prepublish": "npm run build" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/sooro-io/react-gtm-module" 17 | }, 18 | "author": { 19 | "name": "Daniel Bartylla", 20 | "email": "daniel@sooro.io", 21 | "url": "https://github.com/daniel-bartylla" 22 | }, 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/sooro-io/react-gtm-module/issues" 26 | }, 27 | "homepage": "", 28 | "keywords": [ 29 | "react", 30 | "reactjs", 31 | "react-component", 32 | "google tag manager", 33 | "tag manager", 34 | "gtm" 35 | ], 36 | "devDependencies": { 37 | "@babel/cli": "^7.26.4", 38 | "@babel/core": "^7.26.0", 39 | "@babel/eslint-parser": "^7.25.9", 40 | "@babel/preset-env": "^7.26.0", 41 | "@babel/preset-react": "^7.26.3", 42 | "babel-jest": "^29.7.0", 43 | "eslint": "^8.57.1", 44 | "jest": "^29.7.0", 45 | "jest-environment-jsdom": "^29.7.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Snippets.js: -------------------------------------------------------------------------------- 1 | import warn from './utils/warn' 2 | 3 | // https://developers.google.com/tag-manager/quickstart 4 | 5 | const Snippets = { 6 | tags: function ({ 7 | id, 8 | events, 9 | dataLayer, 10 | dataLayerName, 11 | preview = undefined, 12 | auth = undefined, 13 | nonce = undefined, 14 | source, 15 | }) { 16 | if (!id) warn('GTM Id is required') 17 | 18 | const url = new URL(source) 19 | const environment = 20 | auth && preview 21 | ? `>m_auth=${auth}>m_preview=${preview}>m_cookies_win=x` 22 | : '' 23 | 24 | const iframe = ` 25 | ` 27 | 28 | const script = ` 29 | (function(w,d,s,l,i){w[l]=w[l]||[]; 30 | w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'}${ 31 | events.length > 0 32 | ? ',' + JSON.stringify(events).slice(1, -1) 33 | : '' 34 | }); 35 | var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:''; 36 | j.async=true; 37 | j.src='${source}?id='+i+dl+'${environment}'; 38 | ${nonce ? `j.setAttribute('nonce','${nonce}');` : ''} 39 | f.parentNode.insertBefore(j,f); 40 | })(window,document,'script','${dataLayerName}','${id}');` 41 | 42 | const dataLayerVar = this.dataLayer(dataLayer, dataLayerName) 43 | 44 | return { 45 | iframe, 46 | script, 47 | dataLayerVar, 48 | } 49 | }, 50 | dataLayer: function (dataLayer, dataLayerName) { 51 | return ` 52 | window.${dataLayerName} = window.${dataLayerName} || []; 53 | window.${dataLayerName}.push(${JSON.stringify(dataLayer)})` 54 | }, 55 | } 56 | 57 | module.exports = Snippets 58 | -------------------------------------------------------------------------------- /src/TagManager.js: -------------------------------------------------------------------------------- 1 | import Snippets from './Snippets' 2 | 3 | const TagManager = { 4 | dataScript: function (dataLayer, dataLayerName, nonce) { 5 | const script = document.createElement('script') 6 | script.innerHTML = dataLayer 7 | script.setAttribute('data-testid', dataLayerName) 8 | if (nonce) { 9 | script.setAttribute('nonce', nonce) 10 | } 11 | return script 12 | }, 13 | gtm: function (args) { 14 | const snippets = Snippets.tags(args) 15 | 16 | const noScript = () => { 17 | const noscript = document.createElement('noscript') 18 | noscript.innerHTML = snippets.iframe 19 | return noscript 20 | } 21 | 22 | const script = () => { 23 | const script = document.createElement('script') 24 | script.innerHTML = snippets.script 25 | if (args.nonce) { 26 | script.setAttribute('nonce', args.nonce) 27 | } 28 | return script 29 | } 30 | 31 | const dataScript = this.dataScript(snippets.dataLayerVar, args.dataLayerName, args.nonce) 32 | 33 | return { 34 | noScript, 35 | script, 36 | dataScript, 37 | } 38 | }, 39 | initialize: function ({ 40 | gtmId, 41 | events = [], 42 | dataLayer = undefined, 43 | dataLayerName = 'dataLayer', 44 | auth = undefined, 45 | preview = undefined, 46 | nonce = undefined, 47 | source = 'https://googletagmanager.com/gtm.js', 48 | }) { 49 | const gtm = this.gtm({ 50 | id: gtmId, 51 | events, 52 | dataLayer, 53 | dataLayerName, 54 | auth, 55 | preview, 56 | nonce, 57 | source, 58 | }) 59 | if (dataLayer) document.head.appendChild(gtm.dataScript) 60 | document.head.insertBefore(gtm.script(), document.head.childNodes[0]) 61 | document.body.insertBefore(gtm.noScript(), document.body.childNodes[0]) 62 | }, 63 | dataLayer: function ({ dataLayer, dataLayerName = 'dataLayer' }) { 64 | if (window[dataLayerName]) return window[dataLayerName].push(dataLayer) 65 | const snippets = Snippets.dataLayer(dataLayer, dataLayerName) 66 | const dataScript = this.dataScript(snippets, dataLayerName) 67 | document.head.insertBefore(dataScript, document.head.childNodes[0]) 68 | }, 69 | } 70 | 71 | module.exports = TagManager 72 | -------------------------------------------------------------------------------- /src/__tests__/Snippets.spec.js: -------------------------------------------------------------------------------- 1 | import Snippets from '../Snippets' 2 | 3 | let args 4 | let snippets 5 | 6 | describe('Snippets', () => { 7 | beforeEach(() => { 8 | args = { 9 | id: 'GTM-xxxxxx', 10 | dataLayerName: 'dataLayer', 11 | events: {}, 12 | source: 'https://googletagmanager.com/gtm.js', 13 | } 14 | snippets = Snippets.tags(args) 15 | }) 16 | 17 | it('should use the `id` for the iframe', () => { 18 | expect(snippets.iframe).toContain(`id=${args.id}`, 1) 19 | }) 20 | 21 | it('should use the `gtm_auth` and `gtm_preview` for the iframe', () => { 22 | Object.assign(args, { 23 | auth: '6sBOnZx1hqPcO01xPOytLK', 24 | preview: 'env-2', 25 | }) 26 | snippets = Snippets.tags(args) 27 | expect(snippets.iframe).toContain(`gtm_auth=${args.auth}`, 1) 28 | expect(snippets.iframe).toContain(`gtm_preview=${args.preview}`, 1) 29 | }) 30 | 31 | it('should use the `dataLayer` for the script', () => { 32 | args = { dataLayer: { name: 'test' } } 33 | snippets = Snippets.dataLayer(args) 34 | expect(snippets).toContain('{"name":"test"}') 35 | }) 36 | 37 | it('should use the `dataLayerName` for the script', () => { 38 | args = { dataLayerName: 'customName' } 39 | snippets = Snippets.dataLayer(args) 40 | expect(snippets).toContain('customName') 41 | }) 42 | 43 | it('no id provided should log a warn', () => { 44 | console.warn = jest.fn() 45 | const noIdArgs = { 46 | dataLayerName: 'dataLayer', 47 | events: {}, 48 | source: 'https://googletagmanager.com/gtm.js', 49 | } 50 | Snippets.tags(noIdArgs) 51 | expect(console.warn).toBeCalled() 52 | }) 53 | 54 | it('should use the nonce for the script', () => { 55 | const nonce = 'pKFLb6zigj6vHak2TVeKx' 56 | Object.assign(args, { nonce }) 57 | snippets = Snippets.tags(args) 58 | expect(snippets.script).toContain(`setAttribute('nonce','${nonce}')`, 1) 59 | }) 60 | 61 | it('should use the source URL in iframe', () => { 62 | const source = 'https://tracking.example.com/gtm.js' 63 | const url = new URL(source) 64 | Object.assign(args, { source }) 65 | snippets = Snippets.tags(args) 66 | expect(snippets.iframe).toContain(`src="${url.origin}/ns.html`) 67 | }) 68 | 69 | it('should use the source URL in script', () => { 70 | const source = 'https://tracking.example.com/gtm.js' 71 | Object.assign(args, { source }) 72 | snippets = Snippets.tags(args) 73 | expect(snippets.script).toContain(`src='${source}?id=`) 74 | }) 75 | 76 | it('should use the events in the script', () => { 77 | const events = [{ event: 'test', value: 100 }] 78 | Object.assign(args, { events }) 79 | snippets = Snippets.tags(args) 80 | expect(snippets.script).toContain(JSON.stringify(events).slice(1, -1)) 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /src/__tests__/TagManager.spec.js: -------------------------------------------------------------------------------- 1 | import TagManager from '../TagManager' 2 | 3 | describe('TagManager', () => { 4 | // Cleans the environment to ensure tests do not reference resources from previous tests (such as script tags) 5 | beforeEach(() => { 6 | window['dataLayer'] = undefined 7 | window.document.body.innerHTML = '' 8 | window.document.head.innerHTML = '' 9 | }) 10 | 11 | it('should render tagmanager', () => { 12 | TagManager.initialize({ gtmId: 'GTM-xxxxxx' }) 13 | expect(window.dataLayer).toHaveLength(1) 14 | }) 15 | 16 | it('should render datalayer', () => { 17 | const dataLayer = { 18 | userInfo: 'userInfo', 19 | } 20 | const gtmArgs = { 21 | gtmId: 'GTM-xxxxxx', 22 | dataLayer, 23 | } 24 | TagManager.initialize(gtmArgs) 25 | expect(window.dataLayer[0]).toEqual(dataLayer) 26 | const dataScript = window.document.querySelector('[data-testid="dataLayer"]') 27 | expect(dataScript.nonce).toBe('') 28 | }) 29 | 30 | it('should render datalayer script with nonce', () => { 31 | const dataLayer = { 32 | userInfo: 'userInfo', 33 | } 34 | const gtmArgs = { 35 | gtmId: 'GTM-xxxxxx', 36 | dataLayer, 37 | nonce: 'foo', 38 | } 39 | TagManager.initialize(gtmArgs) 40 | expect(window.dataLayer[0]).toEqual(dataLayer) 41 | const dataScript = window.document.querySelector('[data-testid="dataLayer"]') 42 | expect(dataScript.nonce).toBe('foo') 43 | }) 44 | 45 | it('should render nonce', () => { 46 | TagManager.initialize({ gtmId: 'GTM-xxxxxx', nonce: 'foo' }) 47 | const scripts = window.document.getElementsByTagName('script') 48 | expect(scripts[0].nonce).toBe('foo') 49 | }) 50 | 51 | it('should use custom dataLayer name', () => { 52 | const dataLayerName = 'customName' 53 | TagManager.initialize({ gtmId: 'GTM-xxxxxx', dataLayerName }) 54 | expect(window[dataLayerName]).not.toBeUndefined() 55 | expect(window[dataLayerName]).toHaveLength(1) 56 | }) 57 | 58 | it('should add an event to dataLayer', () => { 59 | TagManager.initialize({ gtmId: 'GTM-xxxxxx' }) 60 | TagManager.dataLayer({ dataLayer: { event: 'test' } }) 61 | expect(window['dataLayer']).toHaveLength(2) 62 | }) 63 | 64 | it('should create non-existing dataLayer', () => { 65 | TagManager.dataLayer({ dataLayer: { event: 'test' } }) 66 | expect(window['dataLayer']).not.toBeUndefined() 67 | expect(window['dataLayer']).toHaveLength(1) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /src/__tests__/warn.spec.js: -------------------------------------------------------------------------------- 1 | import warn from '../utils/warn' 2 | 3 | describe('warn()', function () { 4 | it('should append [react-gtm-module] to warning messages', () => { 5 | global.console = { warn: jest.fn() } 6 | warn('foo') 7 | expect(console.warn).toHaveBeenCalledWith('[react-gtm-module]', 'foo') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import TagManager from './TagManager' 2 | 3 | module.exports = TagManager 4 | -------------------------------------------------------------------------------- /src/utils/warn.js: -------------------------------------------------------------------------------- 1 | const warn = (s) => { 2 | console.warn('[react-gtm-module]', s) 3 | } 4 | 5 | module.exports = warn 6 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare const TagManager: { 2 | dataLayer: (dataLayerArgs: DataLayerArgs) => void 3 | initialize: (tagManagerArgs: TagManagerArgs) => void 4 | } 5 | 6 | export interface TagManagerArgs { 7 | /** 8 | * The ID of your GTM Container. 9 | */ 10 | gtmId: string 11 | /** 12 | * Information that should be added to the dataLayer before initialization. 13 | */ 14 | dataLayer?: Record 15 | /** 16 | * Customize the name of the dataLayer object. 17 | */ 18 | dataLayerName?: string 19 | /** 20 | * Additional events which will be added to the dataLayer during initialization. 21 | */ 22 | events?: Array> 23 | /** 24 | * If you use GTM's environment function, you need to pass the gtm_auth query parameter here. 25 | */ 26 | auth?: string 27 | /** 28 | * If you use GTM's environment function, you need to pass the gtm_preview query parameter here. 29 | */ 30 | preview?: string 31 | /** 32 | * Set the nonce [if you use the Google Tag Manager with a Content Security Policy](https://developers.google.com/tag-platform/security/guides/csp). 33 | */ 34 | nonce?: string 35 | /** 36 | * Customize the GTM script URL [if you serve the script through your tagging servers](https://developers.google.com/tag-platform/tag-manager/server-side/dependency-serving?tag=gtm) or want to mask your GTM script. 37 | */ 38 | source?: string 39 | } 40 | 41 | export interface DataLayerArgs { 42 | /** 43 | * Object that contains all of the information that you want to pass to Google Tag Manager. 44 | */ 45 | dataLayer?: Record 46 | /** 47 | * Custom name for dataLayer object. 48 | */ 49 | dataLayerName?: string 50 | } 51 | 52 | export default TagManager 53 | --------------------------------------------------------------------------------