---
title: "Google Tag Manager"
description: "Use Google Tag Manager in your Nuxt app."
canonical_url: "https://scripts.nuxt.com/scripts/google-tag-manager"
last_updated: "2026-05-03T02:50:08.064Z"
---

[Google Tag Manager](https://marketingplatform.google.com/about/tag-manager/) is a tag management system that allows you to quickly and easily update tags and code snippets on your website or mobile app, such as those intended for traffic analysis and marketing optimization.

<callout>

You may not need Google Tag Manager with Nuxt Scripts. GTM is 82kb and will slow down your site.
Nuxt Scripts provides many features you can easily
implement within your Nuxt app. If you're using GTM for Google Analytics, you can use the [`useScriptGoogleAnalytics()`](/scripts/google-analytics) composable instead.

</callout>

<script-stats>



</script-stats>

<script-docs>



</script-docs>

### Guide: Sending Page Events

If you'd like to manually send page events to Google Tag Manager, you can use the `proxy` with the [`useScriptEventPage()`](/docs/api/use-script-event-page) composable.
This composable triggers the provided function on route change after Nuxt updates the page title.

```ts
const { proxy } = useScriptGoogleTagManager({
  id: 'YOUR_ID' // id is only needed if you haven't configured globally
})

useScriptEventPage(({ title, path }) => {
  // triggered on route change after title is updated
  proxy.dataLayer.push({
    event: 'pageview',
    title,
    path
  })
})
```

## Consent Mode

Google Tag Manager natively consumes [GCMv2 consent state](https://developers.google.com/tag-platform/security/guides/consent?consentmode=basic). Set the default with `defaultConsent` (pushes `['consent','default', state]` onto the dataLayer before the `gtm.js` event) and call `consent.update()` at runtime. Pass an **array** to `defaultConsent` to fire multiple defaults, for example [region-specific defaults](https://developers.google.com/tag-platform/security/guides/consent?consentmode=advanced#region-specific-behavior) where each entry targets different countries via `region`.

<callout icon="i-heroicons-play" target="_blank" to="https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent">

Try the live [Cookie Consent Example](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent), [Granular Consent Example](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/granular-consent), or [Regional Consent Example](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/regional-consent) on [StackBlitz](https://stackblitz.com).

</callout>

### Consent Mode v2 Signals

<table>
<thead>
  <tr>
    <th>
      Signal
    </th>
    
    <th>
      Purpose
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        ad_storage
      </code>
    </td>
    
    <td>
      Cookies for advertising
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ad_user_data
      </code>
    </td>
    
    <td>
      Send user data to Google for ads
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ad_personalization
      </code>
    </td>
    
    <td>
      Personalized ads (remarketing)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        analytics_storage
      </code>
    </td>
    
    <td>
      Cookies for analytics
    </td>
  </tr>
</tbody>
</table>

### Example

```vue
<script setup lang="ts">
const { proxy, consent } = useScriptGoogleTagManager({
  id: 'GTM-XXXXXX',
  defaultConsent: {
    ad_storage: 'denied',
    ad_user_data: 'denied',
    ad_personalization: 'denied',
    analytics_storage: 'denied',
  },
})

function acceptAll() {
  consent.update({
    ad_storage: 'granted',
    ad_user_data: 'granted',
    ad_personalization: 'granted',
    analytics_storage: 'granted',
  })
}

function savePreferences(choices: { analytics: boolean, marketing: boolean }) {
  consent.update({
    analytics_storage: choices.analytics ? 'granted' : 'denied',
    ad_storage: choices.marketing ? 'granted' : 'denied',
    ad_user_data: choices.marketing ? 'granted' : 'denied',
    ad_personalization: choices.marketing ? 'granted' : 'denied',
  })
}

useScriptEventPage(({ title, path }) => {
  proxy.dataLayer.push({ event: 'pageview', title, path })
})
</script>
```

### Per-region defaults

Pass an array to `defaultConsent` to fire one `['consent','default', state]` push per entry, in order. This matches Google's [region-specific consent pattern](https://developers.google.com/tag-platform/security/guides/consent?consentmode=advanced#region-specific-behavior): more specific regions (e.g. `US-CA`) override broader ones (`US`); an entry with no `region` is the unscoped global fallback.

```vue
<script setup lang="ts">
useScriptGoogleTagManager({
  id: 'GTM-XXXXXX',
  defaultConsent: [
    {
      // EEA + UK + Switzerland — start denied, wait 500ms for the user's choice
      ad_storage: 'denied',
      ad_user_data: 'denied',
      ad_personalization: 'denied',
      analytics_storage: 'denied',
      region: ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'GB', 'IS', 'LI', 'NO', 'CH'],
      wait_for_update: 500,
    },
    {
      // Everywhere else — granted by default
      ad_storage: 'granted',
      ad_user_data: 'granted',
      ad_personalization: 'granted',
      analytics_storage: 'granted',
    },
  ],
})
</script>
```

The module forwards each entry verbatim, in input order. Precedence between region-scoped and unscoped defaults is enforced by gtag at runtime, not by ordering.

`consent.update()` accepts any `Partial<ConsentState>`; missing categories stay at their current value. `onBeforeGtmStart` remains available as a general escape hatch for any other pre-`gtm.start` setup (only when the GTM ID is passed directly to the composable, not via `nuxt.config`).

<script-types>



</script-types>

## Examples

### Server-Side GTM Setup

Server-side GTM moves tag execution to your server for better privacy, performance (~500ms faster), and ad-blocker bypass.

**Prerequisites:** [Server-side GTM container](https://tagmanager.google.com), hosting ([Cloud Run](https://developers.google.com/tag-platform/tag-manager/server-side/cloud-run-setup-guide) / [Docker](https://developers.google.com/tag-platform/tag-manager/server-side/manual-setup-guide)), and a custom domain.

#### Configuration

Override the script source with your custom domain:

```ts
// nuxt.config.ts
export default defineNuxtConfig({
  scripts: {
    registry: {
      googleTagManager: {
        id: 'GTM-XXXXXX',
        scriptInput: {
          src: 'https://gtm.example.com/gtm.js'
        }
      }
    }
  }
})
```

For environment tokens (`auth`, `preview`), find them in GTM: Admin > Environments > Get Snippet.

#### Troubleshooting

<table>
<thead>
  <tr>
    <th>
      Issue
    </th>
    
    <th>
      Cause
    </th>
    
    <th>
      Solution
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Script blocked by ad blocker
    </td>
    
    <td>
      Custom domain detected as tracker
    </td>
    
    <td>
      Use a non-obvious subdomain name (avoid <code>
        gtm
      </code>
      
      , <code>
        analytics
      </code>
      
      , <code>
        tracking
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      Cookies expire after 7 days in Safari
    </td>
    
    <td>
      ITP treats subdomain as third-party
    </td>
    
    <td>
      Use same-origin setup or implement cookie keeper
    </td>
  </tr>
  
  <tr>
    <td>
      Preview mode not working
    </td>
    
    <td>
      Missing or incorrect auth/preview tokens
    </td>
    
    <td>
      Copy tokens from GTM: Admin > Environments > Get Snippet
    </td>
  </tr>
  
  <tr>
    <td>
      CORS errors
    </td>
    
    <td>
      Server container misconfigured
    </td>
    
    <td>
      Ensure your server container allows requests from your domain
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gtm.js
      </code>
      
       returns 404
    </td>
    
    <td>
      Incorrect path mapping
    </td>
    
    <td>
      Verify your CDN/proxy routes <code>
        /gtm.js
      </code>
      
       to the container
    </td>
  </tr>
</tbody>
</table>

For infrastructure setup, see [Cloud Run](https://developers.google.com/tag-platform/tag-manager/server-side/cloud-run-setup-guide) or [Docker](https://developers.google.com/tag-platform/tag-manager/server-side/manual-setup-guide) guides.
