Requirements
- Target platform
- OpenClaw
- Install method
- Manual import
- Extraction
- Extract archive
- Prerequisites
- OpenClaw
- Primary doc
- SKILL.md
Add Google Analytics 4 tracking to any project. Detects framework, adds tracking code, sets up events, and configures privacy settings.
Add Google Analytics 4 tracking to any project. Detects framework, adds tracking code, sets up events, and configures privacy settings.
Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.
I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete.
I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run.
You are setting up Google Analytics 4 (GA4) for a project. Follow this comprehensive guide to add analytics properly.
Parse the following from $ARGUMENTS: Measurement ID: Format G-XXXXXXXXXX (required, ask if not provided) --events: Include custom event tracking helpers --consent: Include cookie consent integration --debug: Enable debug mode for development
Scan the project to determine the framework/setup: Priority detection order: 1. next.config.js/ts → Next.js 2. nuxt.config.js/ts → Nuxt.js 3. astro.config.mjs → Astro 4. svelte.config.js → SvelteKit 5. remix.config.js → Remix 6. gatsby-config.js → Gatsby 7. vite.config.js + src/App.vue → Vue + Vite 8. vite.config.js + src/App.tsx → React + Vite 9. angular.json → Angular 10. package.json with "react-scripts" → Create React App 11. index.html only → Plain HTML 12. _app.tsx/jsx → Next.js (App Router check: app/ directory) Also check for: TypeScript usage (tsconfig.json) Existing analytics (search for gtag, GA, analytics) Package manager (pnpm-lock.yaml, yarn.lock, package-lock.json)
The Measurement ID must: Start with G- (GA4 format) Be followed by exactly 10 alphanumeric characters Example: G-ABC1234567 If the user provides a UA- ID, inform them: "You provided a Universal Analytics ID (UA-). GA4 uses Measurement IDs starting with 'G-'. Universal Analytics was sunset in July 2024. You'll need to create a GA4 property at analytics.google.com"
Create app/layout.tsx modification or create components/GoogleAnalytics.tsx: // components/GoogleAnalytics.tsx 'use client' import Script from 'next/script' interface GoogleAnalyticsProps { measurementId: string } export function GoogleAnalytics({ measurementId }: GoogleAnalyticsProps) { return ( <> <Script src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`} strategy="afterInteractive" /> <Script id="google-analytics" strategy="afterInteractive"> {` window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', '${measurementId}'); `} </Script> </> ) } Add to root layout: // app/layout.tsx import { GoogleAnalytics } from '@/components/GoogleAnalytics' // Add inside <body> or <html>: <GoogleAnalytics measurementId="G-XXXXXXXXXX" />
Modify pages/_app.tsx: // pages/_app.tsx import type { AppProps } from 'next/app' import Script from 'next/script' const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID export default function App({ Component, pageProps }: AppProps) { return ( <> <Script src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`} strategy="afterInteractive" /> <Script id="google-analytics" strategy="afterInteractive"> {` window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', '${GA_MEASUREMENT_ID}'); `} </Script> <Component {...pageProps} /> </> ) }
Create src/lib/analytics.ts: // src/lib/analytics.ts export const GA_MEASUREMENT_ID = import.meta.env.VITE_GA_MEASUREMENT_ID declare global { interface Window { gtag: (...args: unknown[]) => void dataLayer: unknown[] } } export const initGA = () => { if (typeof window === 'undefined') return const script = document.createElement('script') script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}` script.async = true document.head.appendChild(script) window.dataLayer = window.dataLayer || [] window.gtag = function gtag() { window.dataLayer.push(arguments) } window.gtag('js', new Date()) window.gtag('config', GA_MEASUREMENT_ID) } export const pageview = (url: string) => { window.gtag('config', GA_MEASUREMENT_ID, { page_path: url, }) } export const event = (action: string, params?: Record<string, unknown>) => { window.gtag('event', action, params) } Initialize in src/main.tsx: import { initGA } from './lib/analytics' // Initialize before render if (import.meta.env.PROD) { initGA() }
Create src/plugins/analytics.ts: // src/plugins/analytics.ts import type { App } from 'vue' import type { Router } from 'vue-router' const GA_MEASUREMENT_ID = import.meta.env.VITE_GA_MEASUREMENT_ID declare global { interface Window { gtag: (...args: unknown[]) => void dataLayer: unknown[] } } export const analyticsPlugin = { install(app: App, { router }: { router: Router }) { // Load gtag script const script = document.createElement('script') script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}` script.async = true document.head.appendChild(script) window.dataLayer = window.dataLayer || [] window.gtag = function gtag() { window.dataLayer.push(arguments) } window.gtag('js', new Date()) window.gtag('config', GA_MEASUREMENT_ID) // Track route changes router.afterEach((to) => { window.gtag('config', GA_MEASUREMENT_ID, { page_path: to.fullPath, }) }) // Provide global methods app.config.globalProperties.$gtag = window.gtag } }
Create plugins/analytics.client.ts: // plugins/analytics.client.ts export default defineNuxtPlugin(() => { const config = useRuntimeConfig() const measurementId = config.public.gaMeasurementId if (!measurementId) return // Load gtag useHead({ script: [ { src: `https://www.googletagmanager.com/gtag/js?id=${measurementId}`, async: true, }, { innerHTML: ` window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', '${measurementId}'); `, }, ], }) // Track route changes const router = useRouter() router.afterEach((to) => { window.gtag('config', measurementId, { page_path: to.fullPath, }) }) }) Add to nuxt.config.ts: export default defineNuxtConfig({ runtimeConfig: { public: { gaMeasurementId: process.env.NUXT_PUBLIC_GA_MEASUREMENT_ID, }, }, })
Create src/components/Analytics.astro: --- // src/components/Analytics.astro interface Props { measurementId: string } const { measurementId } = Astro.props --- <script is:inline define:vars={{ measurementId }} src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`} ></script> <script is:inline define:vars={{ measurementId }}> window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', measurementId); </script> Add to layout: --- import Analytics from '../components/Analytics.astro' --- <html> <head> <Analytics measurementId="G-XXXXXXXXXX" /> </head> </html>
Create src/lib/analytics.ts and src/routes/+layout.svelte: // src/lib/analytics.ts import { browser } from '$app/environment' export const GA_MEASUREMENT_ID = import.meta.env.VITE_GA_MEASUREMENT_ID export function initGA() { if (!browser) return const script = document.createElement('script') script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}` script.async = true document.head.appendChild(script) window.dataLayer = window.dataLayer || [] window.gtag = function gtag() { window.dataLayer.push(arguments) } window.gtag('js', new Date()) window.gtag('config', GA_MEASUREMENT_ID) } export function trackPageview(url: string) { if (!browser) return window.gtag('config', GA_MEASUREMENT_ID, { page_path: url }) } <!-- src/routes/+layout.svelte --> <script lang="ts"> import { onMount } from 'svelte' import { page } from '$app/stores' import { initGA, trackPageview } from '$lib/analytics' onMount(() => { initGA() }) $: if ($page.url.pathname) { trackPageview($page.url.pathname) } </script> <slot />
Add to <head>: <!-- Google Analytics --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-XXXXXXXXXX'); </script>
Create or update .env / .env.local: # For Next.js NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX # For Vite (React/Vue/Svelte) VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX # For Nuxt NUXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX Add to .env.example if it exists (without the actual ID): # Google Analytics 4 Measurement ID NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX IMPORTANT: Add .env.local to .gitignore if not already present.
Create a comprehensive events utility: // lib/analytics-events.ts /** * GA4 Event Tracking Utilities * * Recommended events: https://support.google.com/analytics/answer/9267735 */ type GTagEvent = { action: string category?: string label?: string value?: number [key: string]: unknown } // Core event function export const trackEvent = ({ action, category, label, value, ...rest }: GTagEvent) => { if (typeof window === 'undefined' || !window.gtag) return window.gtag('event', action, { event_category: category, event_label: label, value, ...rest, }) } // Engagement events export const trackClick = (elementName: string, location?: string) => { trackEvent({ action: 'click', category: 'engagement', label: elementName, click_location: location, }) } export const trackScroll = (percentage: number) => { trackEvent({ action: 'scroll', category: 'engagement', value: percentage, }) } // Conversion events export const trackSignUp = (method: string) => { trackEvent({ action: 'sign_up', method, }) } export const trackLogin = (method: string) => { trackEvent({ action: 'login', method, }) } export const trackPurchase = (params: { transactionId: string value: number currency: string items?: Array<{ itemId: string itemName: string price: number quantity: number }> }) => { trackEvent({ action: 'purchase', transaction_id: params.transactionId, value: params.value, currency: params.currency, items: params.items, }) } // Content events export const trackSearch = (searchTerm: string) => { trackEvent({ action: 'search', search_term: searchTerm, }) } export const trackShare = (method: string, contentType: string, itemId: string) => { trackEvent({ action: 'share', method, content_type: contentType, item_id: itemId, }) } // Form events export const trackFormStart = (formName: string) => { trackEvent({ action: 'form_start', form_name: formName, }) } export const trackFormSubmit = (formName: string, success: boolean) => { trackEvent({ action: 'form_submit', form_name: formName, success, }) } // Error tracking export const trackError = (errorMessage: string, errorLocation?: string) => { trackEvent({ action: 'exception', description: errorMessage, fatal: false, error_location: errorLocation, }) } // Custom event builder for flexibility export const createCustomEvent = (eventName: string) => { return (params?: Record<string, unknown>) => { trackEvent({ action: eventName, ...params, }) } }
Create a consent-aware wrapper: // lib/analytics-consent.ts type ConsentState = 'granted' | 'denied' interface ConsentConfig { analytics_storage: ConsentState ad_storage: ConsentState ad_user_data: ConsentState ad_personalization: ConsentState } const CONSENT_COOKIE = 'analytics_consent' // Initialize with consent mode export const initWithConsent = (measurementId: string) => { if (typeof window === 'undefined') return // Set default consent state (denied until user consents) window.gtag('consent', 'default', { analytics_storage: 'denied', ad_storage: 'denied', ad_user_data: 'denied', ad_personalization: 'denied', wait_for_update: 500, // Wait for consent banner }) // Load gtag const script = document.createElement('script') script.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}` script.async = true document.head.appendChild(script) window.dataLayer = window.dataLayer || [] window.gtag = function gtag() { window.dataLayer.push(arguments) } window.gtag('js', new Date()) window.gtag('config', measurementId) // Check for existing consent const savedConsent = getCookie(CONSENT_COOKIE) if (savedConsent) { updateConsent(JSON.parse(savedConsent)) } } // Update consent when user makes a choice export const updateConsent = (consent: Partial<ConsentConfig>) => { if (typeof window === 'undefined' || !window.gtag) return const consentState: ConsentConfig = { analytics_storage: consent.analytics_storage || 'denied', ad_storage: consent.ad_storage || 'denied', ad_user_data: consent.ad_user_data || 'denied', ad_personalization: consent.ad_personalization || 'denied', } window.gtag('consent', 'update', consentState) // Save to cookie setCookie(CONSENT_COOKIE, JSON.stringify(consentState), 365) } // Convenience functions export const acceptAll = () => { updateConsent({ analytics_storage: 'granted', ad_storage: 'granted', ad_user_data: 'granted', ad_personalization: 'granted', }) } export const acceptAnalyticsOnly = () => { updateConsent({ analytics_storage: 'granted', ad_storage: 'denied', ad_user_data: 'denied', ad_personalization: 'denied', }) } export const denyAll = () => { updateConsent({ analytics_storage: 'denied', ad_storage: 'denied', ad_user_data: 'denied', ad_personalization: 'denied', }) } // Cookie utilities function setCookie(name: string, value: string, days: number) { const date = new Date() date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000) document.cookie = `${name}=${value};expires=${date.toUTCString()};path=/;SameSite=Lax` } function getCookie(name: string): string | null { const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`)) return match ? match[2] : null }
Add debug configuration: // For development, enable debug mode if (process.env.NODE_ENV === 'development') { window.gtag('config', 'G-XXXXXXXXXX', { debug_mode: true, }) } Also recommend installing the Google Analytics Debugger Chrome extension.
Create types/gtag.d.ts if using TypeScript: // types/gtag.d.ts declare global { interface Window { gtag: Gtag.Gtag dataLayer: object[] } } declare namespace Gtag { interface Gtag { (command: 'config', targetId: string, config?: ConfigParams): void (command: 'set', targetId: string, config: ConfigParams): void (command: 'set', config: ConfigParams): void (command: 'js', date: Date): void (command: 'event', eventName: string, eventParams?: EventParams): void (command: 'consent', consentArg: 'default' | 'update', consentParams: ConsentParams): void (...args: unknown[]): void } interface ConfigParams { page_title?: string page_location?: string page_path?: string send_page_view?: boolean debug_mode?: boolean [key: string]: unknown } interface EventParams { event_category?: string event_label?: string value?: number [key: string]: unknown } interface ConsentParams { analytics_storage?: 'granted' | 'denied' ad_storage?: 'granted' | 'denied' ad_user_data?: 'granted' | 'denied' ad_personalization?: 'granted' | 'denied' wait_for_update?: number } } export {}
After implementation, verify: Measurement ID is correct format (G-XXXXXXXXXX) Script loads in production (check Network tab) Real-time reports show activity in GA4 dashboard Page views are tracked on navigation No console errors related to gtag Environment variables are not committed to git TypeScript has no type errors (if applicable)
After completing setup, provide the user with: Files created/modified (list them) Environment variables needed (with example values) Next steps: Add the Measurement ID to environment variables Deploy and verify in GA4 Real-time reports Set up conversions in GA4 dashboard Consider adding custom events for key user actions
"gtag is not defined" Script hasn't loaded yet; ensure async loading is handled No data in GA4 Check if ad blockers are preventing tracking Verify Measurement ID is correct Check browser console for errors Double page views SPA router sending duplicate events; implement deduplication GDPR Compliance Always implement consent mode for EU users Use the --consent flag to add consent management
Data access, storage, extraction, analysis, reporting, and insight generation.
Largest current source with strong distribution and engagement signals.