Matomo Tag Manager for Nuxt 4.1+, built on top of Nuxt Scripts.
- 🚀 Loads your Matomo Tag Manager container with Nuxt Scripts: non-blocking by default (
onNuxtReady), shows up in Nuxt DevTools, SSR-safe. - 🧭 Single-page app page views that just work: a
page_viewevent with the resolved URL, referrer and final page title is pushed on every navigation, so your Matomo tag never records a stale title. - 🍪 Consent-friendly: set
trigger: 'manual'and load the container once the visitor opts in. - ☁️ Self-hosted and Matomo Cloud: the container URL is resolved for you.
- 🧩 Typed composables for events, goals, custom dimensions, custom variables, site search and the data layer.
- ⚙️ Environment variables: every option can be overridden at runtime with
NUXT_PUBLIC_MATOMO_*.
Install the module (Nuxt Scripts comes with it and is registered for you):
npm install @openmost/nuxt-matomoThen add it to your modules with your Matomo URL and container id:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@openmost/nuxt-matomo'],
matomo: {
host: 'https://matomo.example.com',
containerId: 'aBcD1234',
},
})That's it: the container loads on every page, and a page_view event is pushed on every navigation. Follow Configure your container once in Matomo so those page views are tracked.
| Option | Env var | Default | Description |
|---|---|---|---|
host |
NUXT_PUBLIC_MATOMO_HOST |
none | Your Matomo URL, e.g. https://matomo.example.com or https://example.matomo.cloud. |
containerId |
NUXT_PUBLIC_MATOMO_CONTAINER_ID |
none | Your Tag Manager container id (Tag Manager → Install code). |
trigger |
NUXT_PUBLIC_MATOMO_TRIGGER |
'onNuxtReady' |
When the container loads: 'onNuxtReady', 'client' (as soon as possible) or 'manual'. |
dataLayerName |
NUXT_PUBLIC_MATOMO_DATA_LAYER_NAME |
'dataLayer' |
The queue useDataLayerPush and the page_view event write to ('dataLayer' or '_mtm'). |
pageViewEvent |
none | true |
Push a page_view event to the data layer on every page. |
Environment variables override nuxt.config at runtime, so one build can target several environments:
NUXT_PUBLIC_MATOMO_HOST=https://matomo.example.com
NUXT_PUBLIC_MATOMO_CONTAINER_ID=aBcD1234The container URL is built for you:
host |
Container URL |
|---|---|
https://matomo.example.com |
https://matomo.example.com/js/container_<id>.js |
https://example.matomo.cloud |
https://cdn.matomo.cloud/example.matomo.cloud/container_<id>.js |
If your Install code shows another URL (for example a custom domain on Matomo Cloud), copy it into src:
useScriptMatomoTagManager({
src: 'https://cdn.example.com/container_aBcD1234.js',
})Matomo Tag Manager's default Page view trigger only fires on the first page load. The module pushes a page_view event on every navigation instead, once the destination page has rendered its title:
dataLayer.push({
event: 'page_view',
page_url: '/about', // route fullPath
referrer_url: '/', // previous route fullPath ('' on the first page)
page_title: 'About us', // document.title, after useHead / useSeoMeta
})In your Matomo Tag Manager container:
- Read the data layer. Either enable Synchronize with GTM Data Layer events in the container settings (default
dataLayerName: 'dataLayer'), or setdataLayerName: '_mtm'to push directly into Matomo's own queue. - Create two Data-Layer variables:
DLV - page_url(namepage_url) andDLV - page_title(namepage_title). - Create a Custom Event trigger
CE - page_viewwith the event namepage_view. - Edit your Matomo Analytics tag: set Custom Page URL to
{{DLV - page_url}}, Custom Page Title to{{DLV - page_title}}, and replace its Page view trigger withCE - page_viewso each page is tracked once. - Publish a new container version.
Set trigger: 'manual' so the container never loads on its own, then load it when the visitor accepts:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@openmost/nuxt-matomo'],
matomo: {
host: 'https://matomo.example.com',
containerId: 'aBcD1234',
trigger: 'manual',
},
})<script setup lang="ts">
const { load, status } = useScriptMatomoTagManager()
</script>
<template>
<button v-if="status !== 'loaded'" @click="load()">
Accept analytics
</button>
</template>Commands pushed to _mtm and _paq before consent stay queued and are processed once the container loads.
All composables are auto-imported and safe to call during SSR (they do nothing on the server).
The Nuxt Scripts instance of your container. Without arguments, it returns the container registered by the module.
const { status, load, proxy } = useScriptMatomoTagManager()
// Push to Matomo Tag Manager's queue, even before the container has loaded
proxy._mtm.push({ event: 'newsletter_signup' })Pushes any payload to the configured data layer (window.dataLayer by default).
useDataLayerPush({ event: 'cta_click', label: 'hero' })Tracks an event. The 5th argument holds optional custom dimensions.
useMatomoEvent('Category', 'Action', 'event_name', 23, { dimension1: 'Some value' })Converts a goal, with an optional revenue.
useMatomoGoal(4)
useMatomoGoal(4, 49.9)Sets a custom dimension value.
useMatomoCustomDimension(1, 'premium')Sets a custom variable, scoped to the 'page' (default) or the 'visit'.
useMatomoCustomVariable(1, 'plan', 'pro', 'visit')Tracks an internal site search: keyword, category (or false) and result count.
useMatomoSearch(query.value, 'docs', results.value.length)
useMatomoEvent,useMatomoGoal,useMatomoCustomDimension,useMatomoCustomVariableanduseMatomoSearchpush towindow._paq, which is read by the Matomo Analytics tag of your container.
- Nuxt 4.1 or later is required.
- The container is now loaded by Nuxt Scripts, once the app is ready (
trigger: 'onNuxtReady'). Settrigger: 'client'to load it as early as v2 did. - The
page_viewevent payload is unchanged. SetpageViewEvent: falseto disable it. runtimeConfig.public.matomoand theNUXT_PUBLIC_MATOMO_*env vars keep working.
npm install
npm run dev:prepare # generate type stubs
npm run dev # start the playground on http://localhost:3000
npm run lint
npm run test
npm run test:typesThe playground serves a fake container from playground/public/js, so you can try every feature without a Matomo instance.