Skip to content

Commit 6f54ffe

Browse files
committed
fix(GoogleTagManager): add all query params
Fixes #434
1 parent b4aed99 commit 6f54ffe

File tree

6 files changed

+207
-68
lines changed

6 files changed

+207
-68
lines changed

docs/content/scripts/tracking/google-tag-manager.md

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -121,23 +121,46 @@ interface GoogleTagManagerApi {
121121
You must provide the options when setting up the script for the first time.
122122

123123
```ts
124+
/**
125+
* GTM configuration options with improved documentation
126+
*/
124127
export const GoogleTagManagerOptions = object({
125-
/**
126-
* The Google Tag Manager ID.
127-
*/
128-
id: string(),
129-
/**
130-
* The name of the dataLayer you want to use
131-
* @default 'dataLayer'
132-
*/
133-
dataLayerName: optional(string())
134-
})
128+
/** GTM container ID (format: GTM-XXXXXX) */
129+
id: string(),
130+
131+
/** Optional dataLayer variable name */
132+
l: optional(string()),
133+
134+
/** Authentication token for environment-specific container versions */
135+
auth: optional(string()),
136+
137+
/** Preview environment name */
138+
preview: optional(string()),
139+
140+
/** Forces GTM cookies to take precedence when true */
141+
cookiesWin: optional(union([boolean(), literal('x')])),
142+
143+
/** Enables debug mode when true */
144+
debug: optional(union([boolean(), literal('x')])),
145+
146+
/** No Personal Advertising - disables advertising features when true */
147+
npa: optional(union([boolean(), literal('1')])),
148+
149+
/** Custom dataLayer name (alternative to "l" property) */
150+
dataLayer: optional(string()),
151+
152+
/** Environment name for environment-specific container */
153+
envName: optional(string()),
154+
155+
/** Referrer policy for analytics requests */
156+
authReferrerPolicy: optional(string()),
157+
})
135158
```
136159

137160
### Options types
138161

139162
```ts
140-
type GoogleTagManagerInput = typeof GoogleTagManagerOptions & { onBeforeGtmStart?: ((gtag: Gtag) => void) => void }
163+
type GoogleTagManagerInput = typeof GoogleTagManagerOptions & { onBeforeGtmStart?: (gtag: Gtag) => void }
141164
```
142165
143166
## Example

playground/pages/features/cookie-consent.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function acceptCookies() {
99
showCookieBanner.value = false
1010
}
1111
useScriptGoogleTagManager({
12-
id: 'GTM-5ZQZJZ',
12+
id: 'GTM-MWW974PF',
1313
scriptOptions: {
1414
trigger: scriptConsent,
1515
bundle: true,

playground/pages/features/on-nuxt-ready.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { useScriptGoogleTagManager } from '#imports'
33
44
const { status } = useScriptGoogleTagManager({
5-
id: 'GTM-5ZQZJZ',
5+
id: 'GTM-MWW974PF',
66
scriptOptions: {
77
trigger: 'onNuxtReady', // this is the default behavior
88
bundle: true,

playground/pages/third-parties/google-tag-manager.vue

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,14 @@
11
<script lang="ts" setup>
2-
import { useHead, useScriptGoogleTagManager } from '#imports'
2+
import { useScriptGoogleTagManager } from '#nuxt-scripts/registry/google-tag-manager'
3+
import { useHead } from '#imports'
34
45
useHead({
56
title: 'Google Analytics',
67
})
78
89
// composables return the underlying api as a proxy object and the script state
910
const { proxy, onLoaded, status } = useScriptGoogleTagManager({
10-
id: 'GTM-MNJD4B',
11-
onBeforeGtmStart(gtag) {
12-
gtag('consent', 'default', {
13-
ad_user_data: 'denied',
14-
ad_personalization: 'denied',
15-
ad_storage: 'denied',
16-
analytics_storage: 'denied',
17-
wait_for_update: 500,
18-
})
19-
},
11+
id: 'GTM-MWW974PF',
2012
}) // id is set via runtime config
2113
proxy.dataLayer.push({
2214
event: 'page_view',

src/registry.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,18 @@ export async function registry(resolve?: (path: string, opts?: ResolvePathOption
248248
},
249249
logo: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><path fill="#8AB4F8" d="m150.262 245.516l-44.437-43.331l95.433-97.454l46.007 45.091z"/><path fill="#4285F4" d="M150.45 53.938L106.176 8.731L9.36 104.629c-12.48 12.48-12.48 32.713 0 45.207l95.36 95.986l45.09-42.182l-72.654-76.407z"/><path fill="#8AB4F8" d="m246.625 105.37l-96-96c-12.494-12.494-32.756-12.494-45.25 0c-12.495 12.495-12.495 32.757 0 45.252l96 96c12.494 12.494 32.756 12.494 45.25 0c12.495-12.495 12.495-32.757 0-45.251"/><circle cx="127.265" cy="224.731" r="31.273" fill="#246FDB"/></svg>`,
250250
scriptBundling(options) {
251-
return withQuery('https://www.googletagmanager.com/gtag/js', { id: options?.id, l: options?.l })
251+
return withQuery('https://www.googletagmanager.com/gtm.js', {
252+
id: options.id,
253+
l: options.l,
254+
gtm_auth: options.auth,
255+
gtm_preview: options.preview,
256+
gtm_cookies_win: options.cookiesWin ? 'x' : undefined,
257+
gtm_debug: options.debug ? 'x' : undefined,
258+
gtm_npa: options.npa ? '1' : undefined,
259+
gtm_data_layer: options.dataLayer,
260+
gtm_env: options.envName,
261+
gtm_auth_referrer_policy: options.authReferrerPolicy,
262+
})
252263
},
253264
},
254265
{
Lines changed: 156 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,183 @@
11
import { withQuery } from 'ufo'
22
import type { GTag } from './google-analytics'
33
import { useRegistryScript } from '#nuxt-scripts/utils'
4-
import type { RegistryScriptInput } from '#nuxt-scripts/types'
5-
import { object, string, optional } from '#nuxt-scripts-validator'
6-
7-
type DataLayer = Array<Parameters<GTag> | Record<string, unknown>>
8-
interface GoogleTagManagerDataLayerApi {
9-
name: 'dataLayer'
10-
set: (opt: {
11-
[key: string]: string
12-
}) => void
13-
get: (key: string) => void
4+
import type { NuxtUseScriptOptions, RegistryScriptInput, UseFunctionType, UseScriptContext } from '#nuxt-scripts/types'
5+
import { object, string, optional, boolean, union, literal } from '#nuxt-scripts-validator'
6+
7+
/**
8+
* Improved DataLayer type that better reflects GTM's capabilities
9+
* Can contain either gtag event parameters or custom data objects
10+
*/
11+
export type DataLayerItem = Parameters<GTag> | Record<string, unknown>
12+
export type DataLayer = Array<DataLayerItem>
13+
14+
/**
15+
* DataLayer push function type
16+
*/
17+
export interface DataLayerPush {
18+
(...args: Parameters<GTag>): void
19+
(obj: Record<string, unknown>): void
20+
}
21+
22+
/**
23+
* Improved DataLayer API type with more precise methods
24+
*/
25+
export interface GoogleTagManagerDataLayerApi {
26+
name: string
27+
push: DataLayerPush
28+
set: (config: Record<string, unknown>) => void
29+
get: <T = unknown>(key: string) => T
1430
reset: () => void
31+
listeners: Array<() => void>
1532
}
16-
interface GoogleTagManagerDataLayerStatus {
33+
34+
/**
35+
* DataLayer status information
36+
*/
37+
export interface GoogleTagManagerDataLayerStatus {
1738
dataLayer: {
1839
gtmDom: boolean
1940
gtmLoad: boolean
2041
subscribers: number
42+
[key: string]: unknown
2143
}
2244
}
23-
type GoogleTagManagerInstance = GoogleTagManagerDataLayerStatus & {
24-
[key: string]: {
25-
callback: () => void
26-
dataLayer: GoogleTagManagerDataLayerApi
27-
}
45+
46+
/**
47+
* Container instance type
48+
*/
49+
export interface GoogleTagManagerContainer {
50+
callback: () => void
51+
dataLayer: GoogleTagManagerDataLayerApi
52+
state: Record<string, unknown>
2853
}
29-
interface GoogleTagManagerApi {
54+
55+
/**
56+
* Complete GTM instance object
57+
*/
58+
export interface GoogleTagManagerInstance extends GoogleTagManagerDataLayerStatus {
59+
[containerId: string]: GoogleTagManagerContainer | any
60+
}
61+
62+
/**
63+
* Complete Google Tag Manager API accessible via window
64+
*/
65+
export interface GoogleTagManagerApi {
3066
google_tag_manager: GoogleTagManagerInstance
31-
dataLayer: DataLayer
67+
dataLayer: DataLayer & {
68+
push: DataLayerPush
69+
}
3270
}
3371

72+
/**
73+
* Enhanced window type with GTM
74+
*/
3475
declare global {
3576
interface Window extends GoogleTagManagerApi {}
3677
}
78+
79+
/**
80+
* GTM configuration options with improved documentation
81+
*/
3782
export const GoogleTagManagerOptions = object({
83+
/** GTM container ID (format: GTM-XXXXXX) */
3884
id: string(),
85+
86+
/** Optional dataLayer variable name */
3987
l: optional(string()),
88+
89+
/** Authentication token for environment-specific container versions */
90+
auth: optional(string()),
91+
92+
/** Preview environment name */
93+
preview: optional(string()),
94+
95+
/** Forces GTM cookies to take precedence when true */
96+
cookiesWin: optional(union([boolean(), literal('x')])),
97+
98+
/** Enables debug mode when true */
99+
debug: optional(union([boolean(), literal('x')])),
100+
101+
/** No Personal Advertising - disables advertising features when true */
102+
npa: optional(union([boolean(), literal('1')])),
103+
104+
/** Custom dataLayer name (alternative to "l" property) */
105+
dataLayer: optional(string()),
106+
107+
/** Environment name for environment-specific container */
108+
envName: optional(string()),
109+
110+
/** Referrer policy for analytics requests */
111+
authReferrerPolicy: optional(string()),
40112
})
41113

42114
export type GoogleTagManagerInput = RegistryScriptInput<typeof GoogleTagManagerOptions>
43115

44-
export function useScriptGoogleTagManager<T extends GoogleTagManagerApi>(_options?: GoogleTagManagerInput & { onBeforeGtmStart?: (gtag: GTag) => void }) {
45-
return useRegistryScript<T, typeof GoogleTagManagerOptions>(_options?.key || 'googleTagManager', options => ({
46-
scriptInput: {
47-
src: withQuery('https://www.googletagmanager.com/gtm.js', { id: options?.id, l: options?.l }),
48-
},
49-
schema: import.meta.dev ? GoogleTagManagerOptions : undefined,
50-
scriptOptions: {
51-
use: () => {
52-
return { dataLayer: (window as any)[options.l ?? 'dataLayer'] as DataLayer, google_tag_manager: window.google_tag_manager }
53-
},
54-
performanceMarkFeature: 'nuxt-third-parties-gtm',
55-
tagPriority: 1,
56-
},
57-
clientInit: import.meta.server
58-
? undefined
59-
: () => {
60-
const dataLayerName = options?.l ?? 'dataLayer';
61-
(window as any)[dataLayerName] = (window as any)[(options?.l ?? 'dataLayer')] || []
62-
function gtag() {
63-
// eslint-disable-next-line prefer-rest-params
64-
(window as any)[dataLayerName].push(arguments)
65-
}
66-
_options?.onBeforeGtmStart?.(gtag)
67-
;(window as any)[dataLayerName].push({ 'gtm.start': new Date().getTime(), 'event': 'gtm.js' })
116+
/**
117+
* Hook to use Google Tag Manager in Nuxt applications
118+
*/
119+
export function useScriptGoogleTagManager<T extends GoogleTagManagerApi>(
120+
options?: GoogleTagManagerInput & {
121+
/**
122+
* Optional callback that runs before GTM starts
123+
* Allows for custom initialization or configuration
124+
*/
125+
onBeforeGtmStart?: (gtag: DataLayerPush) => void
126+
},
127+
): UseScriptContext<UseFunctionType<NuxtUseScriptOptions<T>, T>> {
128+
return useRegistryScript<T, typeof GoogleTagManagerOptions>(
129+
options?.key || 'googleTagManager',
130+
(opts) => {
131+
const dataLayerName = opts?.l ?? opts?.dataLayer ?? 'dataLayer'
132+
133+
return {
134+
scriptInput: {
135+
src: withQuery('https://www.googletagmanager.com/gtm.js', {
136+
id: opts.id,
137+
l: opts.l,
138+
gtm_auth: opts.auth,
139+
gtm_preview: opts.preview,
140+
gtm_cookies_win: opts.cookiesWin ? 'x' : undefined,
141+
gtm_debug: opts.debug ? 'x' : undefined,
142+
gtm_npa: opts.npa ? '1' : undefined,
143+
gtm_data_layer: opts.dataLayer,
144+
gtm_env: opts.envName,
145+
gtm_auth_referrer_policy: opts.authReferrerPolicy,
146+
}),
68147
},
69-
}), _options)
148+
schema: import.meta.dev ? GoogleTagManagerOptions : undefined,
149+
scriptOptions: {
150+
use: () => {
151+
return {
152+
dataLayer: (window as any)[dataLayerName] as DataLayer & { push: DataLayerPush },
153+
google_tag_manager: window.google_tag_manager,
154+
}
155+
},
156+
performanceMarkFeature: 'nuxt-third-parties-gtm',
157+
tagPriority: 1,
158+
},
159+
clientInit: import.meta.server
160+
? undefined
161+
: () => {
162+
// Initialize dataLayer if it doesn't exist
163+
(window as any)[dataLayerName] = (window as any)[dataLayerName] || []
164+
165+
// Create gtag function
166+
function gtag(...args: any[]) {
167+
(window as any)[dataLayerName].push(args)
168+
}
169+
170+
// Allow custom initialization
171+
options?.onBeforeGtmStart?.(gtag);
172+
173+
// Push the standard GTM initialization event
174+
(window as any)[dataLayerName].push({
175+
'gtm.start': new Date().getTime(),
176+
'event': 'gtm.js',
177+
})
178+
},
179+
}
180+
},
181+
options,
182+
)
70183
}

0 commit comments

Comments
 (0)