← Back to Skills Marketplace
jeftekhari

Add Analytics

by jeftekhari · GitHub ↗ · v0.1.0
cross-platform ✓ Security Clean
2754
Downloads
1
Stars
7
Active Installs
1
Versions
Install in OpenClaw
/install add-analytics
Description
Add Google Analytics 4 tracking to any project. Detects framework, adds tracking code, sets up events, and configures privacy settings.
README (SKILL.md)

Google Analytics 4 Setup Skill

You are setting up Google Analytics 4 (GA4) for a project. Follow this comprehensive guide to add analytics properly.

Arguments

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

Step 1: Detect Project Type

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)

Step 2: Validate Measurement ID

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"

Step 3: Implementation by Framework

Next.js (App Router - app/ directory)

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 (
    \x3C>
      \x3CScript
        src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}
        strategy="afterInteractive"
      />
      \x3CScript id="google-analytics" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', '${measurementId}');
        `}
      \x3C/Script>
    \x3C/>
  )
}

Add to root layout:

// app/layout.tsx
import { GoogleAnalytics } from '@/components/GoogleAnalytics'

// Add inside \x3Cbody> or \x3Chtml>:
\x3CGoogleAnalytics measurementId="G-XXXXXXXXXX" />

Next.js (Pages Router - pages/ directory)

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 (
    \x3C>
      \x3CScript
        src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`}
        strategy="afterInteractive"
      />
      \x3CScript id="google-analytics" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', '${GA_MEASUREMENT_ID}');
        `}
      \x3C/Script>
      \x3CComponent {...pageProps} />
    \x3C/>
  )
}

React (Vite/CRA)

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\x3Cstring, 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()
}

Vue 3 (Vite)

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
  }
}

Nuxt 3

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,
    },
  },
})

Astro

Create src/components/Analytics.astro:

---
// src/components/Analytics.astro
interface Props {
  measurementId: string
}

const { measurementId } = Astro.props
---

\x3Cscript
  is:inline
  define:vars={{ measurementId }}
  src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}
>\x3C/script>

\x3Cscript is:inline define:vars={{ measurementId }}>
  window.dataLayer = window.dataLayer || [];
  function gtag() {
    dataLayer.push(arguments);
  }
  gtag('js', new Date());
  gtag('config', measurementId);
\x3C/script>

Add to layout:

---
import Analytics from '../components/Analytics.astro'
---
\x3Chtml>
  \x3Chead>
    \x3CAnalytics measurementId="G-XXXXXXXXXX" />
  \x3C/head>
\x3C/html>

SvelteKit

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 })
}
\x3C!-- src/routes/+layout.svelte -->
\x3Cscript 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)
  }
\x3C/script>

\x3Cslot />

Plain HTML

Add to \x3Chead>:

\x3C!-- Google Analytics -->
\x3Cscript async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX">\x3C/script>
\x3Cscript>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
\x3C/script>

Step 4: Environment Variables

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.

Step 5: Event Tracking Helpers (if --events flag)

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\x3C{
    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\x3Cstring, unknown>) => {
    trackEvent({
      action: eventName,
      ...params,
    })
  }
}

Step 6: Cookie Consent Integration (if --consent flag)

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\x3CConsentConfig>) => {
  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
}

Step 7: Debug Mode (if --debug flag)

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.

Step 8: TypeScript Declarations

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 {}

Step 9: Verification Checklist

After implementation, verify:

  1. Measurement ID is correct format (G-XXXXXXXXXX)
  2. Script loads in production (check Network tab)
  3. Real-time reports show activity in GA4 dashboard
  4. Page views are tracked on navigation
  5. No console errors related to gtag
  6. Environment variables are not committed to git
  7. TypeScript has no type errors (if applicable)

Step 10: Summary Output

After completing setup, provide the user with:

  1. Files created/modified (list them)
  2. Environment variables needed (with example values)
  3. 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

Common Issues & Solutions

"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
Usage Guidance
This skill appears to do what it says: it will read your repository to detect framework and then add or modify source files to insert GA4 tracking code. Before running it, (1) back up or run it on a feature branch so you can review changes; (2) confirm the Measurement ID is correct and that you own the analytics property; (3) be aware the inserted code will cause client browsers to load scripts from googletagmanager.com and send analytics data to Google — review privacy/consent requirements (GDPR/CCPA) and enable the --consent option if needed; (4) verify generated changes (env var names, injected snippets) to avoid leaking any private values. No credentials or external installs are requested by the skill itself.
Capability Analysis
Type: OpenClaw Skill Name: add-analytics Version: 0.1.0 The skill bundle is designed to integrate Google Analytics 4 into various web projects. All instructions in SKILL.md, including file system scanning, code modification, and environment variable handling, are directly aligned with this stated purpose. The external network calls are exclusively to legitimate Google Analytics endpoints (www.googletagmanager.com). There is no evidence of data exfiltration, malicious execution, persistence mechanisms, obfuscation, or prompt injection attempts to subvert the agent's intended function or access unrelated sensitive data. The skill even includes good security practices like adding `.env.local` to `.gitignore`.
Capability Assessment
Purpose & Capability
Name/description (Add Google Analytics 4) align with the instructions: the SKILL.md only describes detecting the framework, inserting GA4 snippets, wiring events, and configuring consent. There are no unrelated environment variables, binaries, or installation steps requested.
Instruction Scope
The instructions tell the agent to scan the project repository (package.json, framework config files, src files, tsconfig, lockfiles) and to modify or create source files and env vars to add GA4. This is consistent with the skill's purpose but does mean the skill will read and change project files — the user should expect code modifications and review them. The instructions also add runtime code that will load scripts from googletagmanager.com (expected for GA4).
Install Mechanism
No install spec and no code files are included; this is instruction-only and does not download or install third-party code at install-time, which is the lowest-risk model for this kind of helper.
Credentials
The skill does not request environment variables, credentials, or config paths. Proposed changes in examples use public env var names (e.g., NEXT_PUBLIC_GA_MEASUREMENT_ID or VITE_GA_MEASUREMENT_ID) which are appropriate for front-end tracking and do not require secrets.
Persistence & Privilege
always is false and the skill does not request persistent privileges or modifications to other skills or system-wide settings. It only describes modifying project files within the repository (its intended scope).
How to Use
  1. Make sure OpenClaw is installed (local or Docker)
  2. Run the install command in chat: /install add-analytics
  3. After installation, invoke the skill by name or use /add-analytics
  4. Provide required inputs per the skill's parameter spec and get structured output
Version History
v0.1.0
add-analytics 0.1.0 — Initial release - Adds Google Analytics 4 tracking to projects, with automatic framework detection. - Supports Next.js, Nuxt.js, Astro, SvelteKit, Remix, Gatsby, Vite (React/Vue), Angular, Create React App, and plain HTML. - Validates provided Measurement ID and notifies if an outdated Universal Analytics ID is used. - Optionally includes custom event tracking helpers (--events), cookie consent integration (--consent), and debug mode (--debug). - Detects TypeScript usage, existing analytics integrations, and package manager.
Metadata
Slug add-analytics
Version 0.1.0
License
All-time Installs 7
Active Installs 7
Total Versions 1
Frequently Asked Questions

What is Add Analytics?

Add Google Analytics 4 tracking to any project. Detects framework, adds tracking code, sets up events, and configures privacy settings. It is an AI Agent Skill for Claude Code / OpenClaw, with 2754 downloads so far.

How do I install Add Analytics?

Run "/install add-analytics" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.

Is Add Analytics free?

Yes, Add Analytics is completely free (open-source). You can download, install and use it at no cost.

Which platforms does Add Analytics support?

Add Analytics is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).

Who created Add Analytics?

It is built and maintained by jeftekhari (@jeftekhari); the current version is v0.1.0.

💬 Comments