Install Google Tag Manager with Sanity
Google Tag Manager (GTM) allows you to manage all your tracking tags from a single interface without editing code. For Sanity-powered sites, GTM is installed in your frontend framework, not in Sanity Studio itself.
Why Use GTM with Sanity?
Benefits:
- Centralized tag management - All tracking tags in one place
- No code deployments - Update tracking without redeploying your app
- Version control - Track changes to tags and triggers
- Testing tools - Preview mode to test before publishing
- Collaboration - Multiple team members can manage tags
- Performance - Single container load vs. multiple scripts
Best for:
- Marketing teams that need to update tracking frequently
- Sites with multiple analytics platforms (GA4, Meta, TikTok, etc.)
- A/B testing and conversion rate optimization
- Dynamic event tracking based on user behavior
Before You Begin
Prerequisites:
- Google Tag Manager account (free at tagmanager.google.com)
- GTM Container ID (format:
GTM-XXXXXXX) - Frontend framework with Sanity integrated
- Developer access to your codebase
Implementation by Framework
Method 1: Next.js (App Router)
For Next.js 13+ with the App Router.
1. Create GTM Component
Create app/components/GoogleTagManager.tsx:
'use client'
import Script from 'next/script'
export function GoogleTagManager() {
const GTM_ID = process.env.NEXT_PUBLIC_GTM_ID
if (!GTM_ID) {
console.warn('GTM ID not found')
return null
}
return (
<>
{/* GTM Script */}
<Script
id="gtm-script"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','${GTM_ID}');
`,
}}
/>
</>
)
}
export function GoogleTagManagerNoScript() {
const GTM_ID = process.env.NEXT_PUBLIC_GTM_ID
if (!GTM_ID) return null
return (
<noscript>
<iframe
src={`https://www.googletagmanager.com/ns.html?id=${GTM_ID}`}
height="0"
width="0"
style={{ display: 'none', visibility: 'hidden' }}
/>
</noscript>
)
}
2. Add to Root Layout
Update app/layout.tsx:
import { GoogleTagManager, GoogleTagManagerNoScript } from './components/GoogleTagManager'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<GoogleTagManager />
</head>
<body>
<GoogleTagManagerNoScript />
{children}
</body>
</html>
)
}
3. Set Environment Variables
Create .env.local:
NEXT_PUBLIC_GTM_ID=GTM-XXXXXXX
4. Initialize Data Layer (Optional)
Create app/components/DataLayerInit.tsx:
'use client'
import { useEffect } from 'react'
export function DataLayerInit() {
useEffect(() => {
window.dataLayer = window.dataLayer || []
window.dataLayer.push({
event: 'dataLayer_initialized',
platform: 'sanity',
framework: 'nextjs',
})
}, [])
return null
}
Method 2: Next.js (Pages Router)
For Next.js using Pages Router.
1. Update _document.tsx
Create or update pages/_document.tsx:
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
const GTM_ID = process.env.NEXT_PUBLIC_GTM_ID
return (
<Html>
<Head>
{/* GTM Script */}
<script
dangerouslySetInnerHTML={{
__html: `
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','${GTM_ID}');
`,
}}
/>
</Head>
<body>
{/* GTM noscript */}
<noscript>
<iframe
src={`https://www.googletagmanager.com/ns.html?id=${GTM_ID}`}
height="0"
width="0"
style={{ display: 'none', visibility: 'hidden' }}
/>
</noscript>
<Main />
<NextScript />
</body>
</Html>
)
}
2. Track Route Changes
Update pages/_app.tsx:
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
const router = useRouter()
useEffect(() => {
const handleRouteChange = (url: string) => {
window.dataLayer?.push({
event: 'pageview',
page: url,
})
}
router.events.on('routeChangeComplete', handleRouteChange)
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [router.events])
return <Component {...pageProps} />
}
Method 3: Gatsby
Perfect for static Sanity sites.
1. Install Plugin
npm install gatsby-plugin-google-tagmanager
2. Configure in gatsby-config.js
module.exports = {
plugins: [
{
resolve: 'gatsby-source-sanity',
options: {
projectId: process.env.SANITY_PROJECT_ID,
dataset: process.env.SANITY_DATASET,
},
},
{
resolve: 'gatsby-plugin-google-tagmanager',
options: {
id: process.env.GTM_ID,
// Include GTM in development (optional)
includeInDevelopment: false,
// Default dataLayer name
defaultDataLayer: { platform: 'sanity' },
// GTM script load timing
enableWebVitalsTracking: true,
// Route change event name
routeChangeEventName: 'gatsby-route-change',
},
},
],
}
3. Set Environment Variables
Create .env.production:
GTM_ID=GTM-XXXXXXX
4. Manual Implementation (Alternative)
If not using plugin, update gatsby-ssr.js:
export const onRenderBody = ({ setHeadComponents, setPreBodyComponents }) => {
const GTM_ID = process.env.GTM_ID
if (!GTM_ID) return
setHeadComponents([
<script
key="gtm-script"
dangerouslySetInnerHTML={{
__html: `
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','${GTM_ID}');
`,
}}
/>,
])
setPreBodyComponents([
<noscript key="gtm-noscript">
<iframe
src={`https://www.googletagmanager.com/ns.html?id=${GTM_ID}`}
height="0"
width="0"
style={{ display: 'none', visibility: 'hidden' }}
/>
</noscript>,
])
}
Track route changes in gatsby-browser.js:
export const onRouteUpdate = ({ location }) => {
if (typeof window !== 'undefined' && window.dataLayer) {
window.dataLayer.push({
event: 'pageview',
page: location.pathname,
})
}
}
Method 4: Nuxt.js
For Vue developers using Sanity.
1. Install Module
npm install @nuxtjs/gtm
2. Configure in nuxt.config.js
export default {
modules: [
'@nuxtjs/gtm',
],
gtm: {
id: process.env.GTM_ID,
enabled: true,
debug: process.env.NODE_ENV !== 'production',
// Auto-track page views
pageTracking: true,
// Optional: Custom pageView event
pageViewEventName: 'nuxtRoute',
// Load GTM script after page load
defer: false,
// Respect Do Not Track
respectDoNotTrack: true,
},
publicRuntimeConfig: {
gtmId: process.env.GTM_ID,
},
}
3. Push Custom Events
<template>
<button @click="trackClick">Click Me</button>
</template>
<script>
export default {
methods: {
trackClick() {
this.$gtm.push({
event: 'button_click',
buttonName: 'cta_button',
})
},
},
}
</script>
Method 5: React SPA (Vite, Create React App)
For single-page applications.
1. Add to index.html
Update public/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Google Tag Manager -->
<script>
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','%VITE_GTM_ID%');
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<!-- Google Tag Manager (noscript) -->
<noscript>
<iframe
src="https://www.googletagmanager.com/ns.html?id=%VITE_GTM_ID%"
height="0"
width="0"
style="display:none;visibility:hidden"
></iframe>
</noscript>
<!-- End Google Tag Manager (noscript) -->
<div id="root"></div>
</body>
</html>
2. Track Route Changes
// App.tsx
import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
function App() {
const location = useLocation()
useEffect(() => {
window.dataLayer?.push({
event: 'pageview',
page: location.pathname,
})
}, [location])
return <Router>{/* Routes */}</Router>
}
3. Use Environment Variables
Create .env:
VITE_GTM_ID=GTM-XXXXXXX
# or for CRA
REACT_APP_GTM_ID=GTM-XXXXXXX
For runtime replacement, use a plugin or replace manually in build process.
Configure GTM Container
1. Create Basic Tags
GA4 Configuration Tag:
- In GTM, go to Tags → New
- Click Tag Configuration → Google Analytics: GA4 Configuration
- Enter your Measurement ID (G-XXXXXXXXXX)
- Triggering: Select All Pages
- Save and name it "GA4 - Configuration"
GA4 Page View Tag:
- Tags → New
- Tag Configuration → Google Analytics: GA4 Event
- Configuration Tag: Select your GA4 Configuration tag
- Event Name:
page_view - Triggering: Custom Event trigger for
pageview(if tracking SPAs) - Save
2. Create Variables for Sanity Data
See GTM Data Layer for Sanity for detailed variable setup.
Common Variables:
- Content Type (Data Layer Variable:
content.type) - Content ID (Data Layer Variable:
content.id) - Content Title (Data Layer Variable:
content.title) - Content Category (Data Layer Variable:
content.category) - Content Author (Data Layer Variable:
content.author) - Content Tags (Data Layer Variable:
content.tags) - Sanity Dataset (Data Layer Variable:
sanity.dataset) - Content Revision (Data Layer Variable:
content.revision)
3. Create Triggers
Page View Trigger (SPA):
- Type: Custom Event
- Event name:
pageview - Use for: SPA route changes
Content View Trigger:
- Type: Custom Event
- Event name:
content_view - Use for: Sanity content engagement
Scroll Depth Trigger:
- Type: Scroll Depth
- Percentages: 25, 50, 75, 90
- Use for: Content engagement tracking
Data Layer Setup with Sanity Content
Basic Data Layer Structure
Push Sanity content metadata to GTM data layer:
'use client'
import { useEffect } from 'react'
export function SanityDataLayer({ document }: { document: any }) {
useEffect(() => {
if (typeof window === 'undefined') return
window.dataLayer = window.dataLayer || []
window.dataLayer.push({
event: 'sanity_content_loaded',
content: {
type: document._type,
id: document._id,
title: document.title,
slug: document.slug?.current,
category: document.category?.title || null,
author: document.author?.name || null,
tags: document.tags?.map(t => t.title) || [],
publishedAt: document.publishedAt || null,
updatedAt: document._updatedAt,
revision: document._rev,
},
sanity: {
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
apiVersion: '2024-01-01',
}
})
}, [document])
return null
}
Portable Text Tracking
Track user engagement with Portable Text content blocks:
1. Track Portable Text Block Views:
import { PortableText, PortableTextComponents } from '@portabletext/react'
import { useEffect, useRef } from 'react'
const components: PortableTextComponents = {
block: {
h2: ({ children, value }) => {
const ref = useRef<HTMLHeadingElement>(null)
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && window.dataLayer) {
window.dataLayer.push({
event: 'portable_text_heading_view',
headingText: children,
blockKey: value._key,
blockType: value._type,
})
}
})
},
{ threshold: 0.5 }
)
if (ref.current) {
observer.observe(ref.current)
}
return () => observer.disconnect()
}, [])
return <h2 ref={ref}>{children}</h2>
},
},
types: {
image: ({ value }) => {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && window.dataLayer) {
window.dataLayer.push({
event: 'portable_text_image_view',
imageAlt: value.alt,
blockKey: value._key,
})
}
})
},
{ threshold: 0.5 }
)
if (ref.current) {
observer.observe(ref.current)
}
return () => observer.disconnect()
}, [])
return (
<div ref={ref}>
<img src={value.asset.url} alt={value.alt} />
</div>
)
},
videoEmbed: ({ value }) => {
return (
<div
onClick={() => {
window.dataLayer?.push({
event: 'portable_text_video_click',
videoUrl: value.url,
videoProvider: value.provider,
blockKey: value._key,
})
}}
>
{/* Video embed component */}
</div>
)
},
ctaBlock: ({ value }) => {
return (
<div
onClick={() => {
window.dataLayer?.push({
event: 'portable_text_cta_click',
ctaText: value.text,
ctaLink: value.link,
blockKey: value._key,
})
}}
>
{/* CTA component */}
</div>
)
},
},
marks: {
link: ({ value, children }) => {
return (
<a
href={value.href}
onClick={() => {
window.dataLayer?.push({
event: 'portable_text_link_click',
linkUrl: value.href,
linkText: children,
linkType: value.href.startsWith('http') ? 'external' : 'internal',
})
}}
>
{children}
</a>
)
},
},
}
export function TrackedPortableText({ value }: { value: any }) {
return <PortableText value={value} components={components} />
}
2. Create GTM Tags for Portable Text Events:
In GTM, create tags that fire on these custom events:
Heading View Tag:
- Trigger: Custom Event =
portable_text_heading_view - Tag Type: GA4 Event
- Event Name:
content_heading_view - Parameters:
heading_text:{{DLV - Heading Text}}block_key:{{DLV - Block Key}}
Image View Tag:
- Trigger: Custom Event =
portable_text_image_view - Tag Type: GA4 Event
- Event Name:
content_image_view
Link Click Tag:
- Trigger: Custom Event =
portable_text_link_click - Tag Type: GA4 Event
- Event Name:
content_link_click - Parameters:
link_url:{{DLV - Link URL}}link_type:{{DLV - Link Type}}
Image Asset Tracking
Track Sanity image asset performance:
import Image from 'next/image'
import { urlFor } from '@/lib/sanity.image'
export function TrackedSanityImage({ image, alt, priority = false }) {
const imageUrl = urlFor(image).width(1200).url()
const assetId = image.asset._ref
return (
<Image
src={imageUrl}
alt={alt || image.alt}
width={1200}
height={675}
priority={priority}
onLoad={() => {
// Track when image loads
window.dataLayer?.push({
event: 'sanity_image_loaded',
imageAssetId: assetId,
imageAlt: alt || image.alt,
imageDimensions: image.asset.metadata?.dimensions,
})
}}
onClick={() => {
// Track image clicks
window.dataLayer?.push({
event: 'sanity_image_click',
imageAssetId: assetId,
imageAlt: alt || image.alt,
})
}}
/>
)
}
Multi-Dataset Considerations
Track which Sanity dataset is being used:
1. Push Dataset to Data Layer:
// app/layout.tsx
'use client'
import { useEffect } from 'react'
export function DatasetTracker() {
useEffect(() => {
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET
window.dataLayer = window.dataLayer || []
window.dataLayer.push({
event: 'sanity_dataset_loaded',
sanity: {
dataset: dataset,
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
environment: dataset === 'production' ? 'production' : 'staging',
}
})
}, [])
return null
}
2. Create GTM Variable for Dataset:
In GTM:
- Variable Type: Data Layer Variable
- Data Layer Variable Name:
sanity.dataset - Default Value:
unknown
3. Conditional Tags Based on Dataset:
Only fire production tags when dataset is production:
Production-Only Tag:
- Trigger: All Pages
- Exception:
{{Sanity Dataset}}does not equalproduction
Staging Tag (Different GA4 Property):
- Trigger: All Pages
- Fires on:
{{Sanity Dataset}}equalsstaging - GA4 Measurement ID: Your staging property ID
4. Multi-Dataset Event Tracking:
export function trackEvent(eventName: string, params: any) {
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET
window.dataLayer?.push({
event: eventName,
...params,
sanity_dataset: dataset,
environment: dataset === 'production' ? 'production' : 'non-production',
})
}
// Usage
trackEvent('content_view', {
content_type: 'post',
content_id: '123',
})
GROQ-Enhanced Data Layer
Use GROQ queries to enrich the data layer:
import { client } from '@/lib/sanity.client'
export async function getEnrichedContent(slug: string) {
const query = `
*[_type == "post" && slug.current == $slug][0] {
_id,
_type,
_rev,
title,
slug,
publishedAt,
// Author details
"author": author-> {
name,
_id,
"postCount": count(*[_type == "post" && references(^._id)])
},
// Categories
"categories": categories[]-> {
title,
_id,
"postCount": count(*[_type == "post" && references(^._id)])
},
// Related posts
"relatedPosts": *[_type == "post" && count((categories[]._ref)[@ in ^.categories[]._ref]) > 0 && _id != ^._id][0...3] {
title,
slug
},
// Content metadata
"metadata": {
"wordCount": length(pt::text(body)),
"estimatedReadTime": round(length(pt::text(body)) / 200),
"imageCount": count(body[_type == "image"]),
"videoCount": count(body[_type == "videoEmbed"]),
"hasCallToAction": defined(body[_type == "ctaBlock"][0])
}
}
`
const content = await client.fetch(query, { slug })
return content
}
// Push enriched data to data layer
export function pushEnrichedDataLayer(content: any) {
window.dataLayer?.push({
event: 'content_loaded',
content: {
id: content._id,
type: content._type,
title: content.title,
slug: content.slug.current,
author: content.author?.name,
authorId: content.author?._id,
authorPostCount: content.author?.postCount,
categories: content.categories?.map(c => c.title) || [],
categoryPostCounts: content.categories?.map(c => c.postCount) || [],
relatedPostCount: content.relatedPosts?.length || 0,
wordCount: content.metadata?.wordCount || 0,
readTime: content.metadata?.estimatedReadTime || 0,
hasImages: (content.metadata?.imageCount || 0) > 0,
hasVideos: (content.metadata?.videoCount || 0) > 0,
hasCTA: content.metadata?.hasCallToAction || false,
}
})
}
Advanced Configuration
Exclude Sanity Studio Sessions
Don't track Sanity Studio sessions:
// Check for Sanity Studio
const isSanityStudio = window.location.pathname.startsWith('/studio')
// Conditionally initialize GTM
if (!isSanityStudio) {
// Initialize GTM script
}
Or use GTM trigger exception:
- Create variable: URL starts with
/studio - Add to trigger exceptions
Multi-Environment Setup
Use different GTM containers for different environments:
const GTM_ID =
process.env.VERCEL_ENV === 'production'
? process.env.NEXT_PUBLIC_GTM_PRODUCTION_ID
: process.env.VERCEL_ENV === 'preview'
? process.env.NEXT_PUBLIC_GTM_STAGING_ID
: process.env.NEXT_PUBLIC_GTM_DEV_ID
Content Security Policy (CSP)
If using CSP headers, allow GTM:
// next.config.js
const ContentSecurityPolicy = `
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com;
connect-src 'self' https://www.google-analytics.com;
img-src 'self' data: https://www.google-analytics.com;
`
module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Content-Security-Policy',
value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim(),
},
],
},
]
},
}
Server-Side GTM (Advanced)
For server-side tagging with Next.js:
// pages/api/gtm-server.ts
export default async function handler(req, res) {
const GTM_SERVER_URL = process.env.GTM_SERVER_CONTAINER_URL
await fetch(GTM_SERVER_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
event_name: req.body.event,
...req.body.params,
}),
})
res.status(200).json({ success: true })
}
Testing & Verification
1. GTM Preview Mode
- In GTM, click Preview
- Enter your site URL
- Navigate your Sanity-powered site
- Verify:
- GTM container loads
- Tags fire correctly
- Variables populate with Sanity data
- Triggers work as expected
2. Browser Console
// Check if dataLayer exists
console.log(window.dataLayer)
// Monitor dataLayer pushes
const originalPush = window.dataLayer.push
window.dataLayer.push = function() {
console.log('DataLayer Push:', arguments[0])
return originalPush.apply(window.dataLayer, arguments)
}
3. Browser Extensions
- Tag Assistant Legacy - View tags on page
- dataLayer Inspector - View dataLayer in real-time
- Google Analytics Debugger - Verify GA4 events
4. Network Tab
Chrome DevTools → Network:
- Filter by
gtmorgoogle-analytics - Verify requests are sent
- Check request payloads
Common GTM Tags for Sanity
Meta Pixel Tag
- Tags → New
- Custom HTML tag
- Add Meta Pixel code:
<script>
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '{{Meta Pixel ID}}');
fbq('track', 'PageView');
</script>
- Triggering: All Pages
- Save
Custom Event Tag
Track Sanity-specific events:
- Tags → New
- Google Analytics: GA4 Event
- Event Name:
content_view - Event Parameters:
content_type:\{\{DLV - Content Type\}\}content_id:\{\{DLV - Content ID\}\}title:\{\{DLV - Content Title\}\}
- Triggering: Custom Event
content_view
Performance Optimization
Async Loading
GTM loads asynchronously by default, but you can optimize further:
// Load GTM after page interactive
<Script
id="gtm"
strategy="afterInteractive" // Next.js
dangerouslySetInnerHTML={{...}}
/>
Lazy Load Tags
In GTM, set tags to fire after specific user interactions rather than immediately.
Minimize Data Layer Size
Only push necessary data to avoid performance impact:
// Good
dataLayer.push({
event: 'content_view',
contentType: 'post',
contentId: '123'
})
// Bad (too much data)
dataLayer.push({
event: 'content_view',
entireSanityDocument: {...} // Too large
})
Troubleshooting
GTM Container Not Loading
Checks:
- GTM ID is correct (format:
GTM-XXXXXXX) - No JavaScript errors in console
- Script isn't blocked by ad blocker
- CSP headers allow GTM
Tags Not Firing
Checks:
- GTM container is published
- Triggers are configured correctly
- Variables return expected values
- Preview mode shows tag should fire
Data Layer Variables Undefined
Checks:
- Data layer is pushed before GTM reads it
- Variable names match exactly
- Data layer structure is correct
See Events Not Firing for detailed debugging.
Next Steps
- Configure Data Layer - Set up custom data layer for Sanity
- Install Meta Pixel - Add Meta Pixel via GTM
- Troubleshoot Tracking - Debug GTM issues
For general GTM concepts, see Google Tag Manager Guide.