npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@connectaryal/google-analytics

v2.0.0

Published

A modern, type-safe, and performance-optimized Google Analytics 4 (GA4) tracking library for React and Next.js applications. Features comprehensive ecommerce tracking, automatic event batching, SSR support, and production-ready error handling. Built with

Readme

@connectaryal/google-analytics

Modern Google Analytics 4 & Google Tag Manager - Type-safe, performance-optimized, production-ready React library.

npm version TypeScript React Next.js MIT License

Why Choose This Library?

Unlike other GA4 libraries, this one is built for modern React applications with enterprise-grade features:

  • Zero Configuration - Works out of the box with sensible defaults
  • 100% Type Safe - Full TypeScript support with intelligent autocomplete
  • Performance Optimized - Event batching, memory leak prevention, and automatic initialization
  • Complete Ecommerce - Enhanced ecommerce tracking with all GA4 events
  • Google Tag Manager - Load a GTM container (gtmId) and push every event to the dataLayer in GA4's GTM format
  • React Hooks - Modern React patterns with custom hooks
  • No Script Conflicts - Prevents duplicate GA script loading
  • Debug Mode - Development-friendly error reporting and validation
  • Event Validation - Automatic parameter validation and sanitization

Quick Start

Installation

npm install @connectaryal/google-analytics
# or
yarn add @connectaryal/google-analytics
# or
pnpm add @connectaryal/google-analytics

Basic Setup

// 1. Wrap your app with GAProvider
import { GAProvider } from "@connectaryal/google-analytics";

function App() {
  const gaConfig = {
    measurementId: "G-XXXXXXXXXX",
    debug: process.env.NODE_ENV === "development",
  };

  return (
    <GAProvider config={gaConfig}>
      <YourApp />
    </GAProvider>
  );
}

Using Google Tag Manager instead? Pass gtmId: "GTM-XXXXXXX". See Google Tag Manager.

Using Analytics Hooks

import {
  useGoogleAnalytics,
  useGAEcommerce,
} from "@connectaryal/google-analytics";

function HomePage() {
  const { trackPage, trackCustomEvent } = useGoogleAnalytics();
  const { trackCart } = useGAEcommerce();

  // Track page views automatically
  useEffect(() => {
    trackPage({ title: "Home Page" });
  }, [trackPage]);

  // Track custom events
  const handleHeroCTA = () => {
    trackCustomEvent({
      name: "hero_cta_click",
      params: {
        button_text: "Get Started",
        section: "hero",
      },
    });
  };

  // Track ecommerce events
  const handleAddToCart = (product) => {
    trackCart({
      action: "add_to_cart",
      items: [
        {
          item_id: product.id,
          item_name: product.name,
          price: product.price,
          quantity: 1,
        },
      ],
      value: product.price,
    });
  };

  return (
    <div>
      <button onClick={handleHeroCTA}>Get Started</button>
      <button onClick={() => handleAddToCart(product)}>Add to Cart</button>
    </div>
  );
}

Core Features

Page Tracking

const { trackPage } = useGoogleAnalytics();

// Basic page tracking
trackPage();

// Custom page tracking with parameters
trackPage({
  path: "/products/wireless-headphones",
  title: "Wireless Headphones - Best Audio Experience",
  referrer: "https://google.com",
});

// Track single-page application navigation
const router = useRouter();
useEffect(() => {
  const handleRouteChange = (url) => {
    trackPage({ path: url });
  };

  router.events.on("routeChangeComplete", handleRouteChange);
  return () => router.events.off("routeChangeComplete", handleRouteChange);
}, [router, trackPage]);

Custom Event Tracking

const {
  trackCustomEvent,
  trackEngagement,
  trackSearch,
  trackFormSubmission,
  trackVideoPlay,
  trackException,
} = useGoogleAnalytics();

// Simple custom event
trackCustomEvent({
  name: "newsletter_signup",
  params: {
    method: "popup",
    location: "homepage",
  },
});

// User engagement tracking
trackEngagement({
  type: "scroll",
  options: {
    scroll_depth: 75,
    page_title: "Product Details",
  },
});

// Search tracking
trackSearch({
  searchTerm: "wireless headphones",
  options: {
    search_results: 24,
    search_category: "electronics",
  },
});

// Form submission tracking
trackFormSubmission({
  formId: "contact_form",
  formData: {
    form_type: "contact",
    user_type: "new_visitor",
  },
});

// Video interaction tracking
trackVideoPlay({
  title: "Product Demo Video",
  url: "https://example.com/video.mp4",
  duration: 120,
  customParams: {
    video_category: "product_demo",
  },
});

// Exception tracking
trackException({
  description: "Payment processing failed",
  fatal: false,
  customParams: {
    error_code: "PAYMENT_001",
    user_id: "user123",
  },
});

Authentication Events

// User login tracking
const { trackLogin, trackSignUp } = useGAAuth();

trackLogin({
  method: "google",
  customParams: {
    user_type: "returning",
  },
});

// User signup tracking
trackSignUp({
  method: "email",
  customParams: {
    campaign: "summer_sale",
  },
});

// Attach a user ID (your own non-PII identifier) and user properties
const { setUserId, setUserProperties } = useGAAuth();

setUserId("user_123"); // gtag("set", { user_id }) or dataLayer.push({ user_id })
setUserProperties({ plan: "pro" }); // gtag("set", "user_properties", …) or dataLayer.push({ user_properties })
setUserId(null); // on logout

Social Sharing

// Track social shares
const { trackShare } = useGoogleAnalytics();

trackShare({
  contentType: "article",
  contentId: "how-to-setup-analytics",
  method: "twitter",
  customParams: {
    share_location: "article_bottom",
  },
});

Complete Ecommerce Tracking

Product Interactions

const { trackItem } = useGAEcommerce();

// View item
trackItem({
  action: "view_item",
  items: [
    {
      item_id: "SKU123",
      item_name: "Wireless Headphones",
      item_category: "Electronics",
      item_brand: "AudioTech",
      price: 99.99,
      quantity: 1,
    },
  ],
  value: 99.99,
  customParams: {
    source: "product_list",
  },
});

// View item list
trackItem({
  action: "view_item_list",
  items: products,
  item_list_id: "electronics_featured",
  item_list_name: "Featured Electronics",
});

// Select item
trackItem({
  action: "select_item",
  items: [selectedProduct],
  item_list_id: "search_results",
  item_list_name: "Search Results",
});

Shopping Cart

const { trackCart } = useGAEcommerce();

// Add to cart
trackCart({
  action: "add_to_cart",
  items: [
    {
      item_id: "SKU123",
      item_name: "Wireless Headphones",
      price: 99.99,
      quantity: 2,
    },
  ],
  value: 199.98,
  customParams: {
    add_source: "product_page",
  },
});

// Remove from cart
trackCart({
  action: "remove_from_cart",
  items: [removedItem],
  value: removedItem.price * removedItem.quantity,
});

// View cart
trackCart({
  action: "view_cart",
  items: cartItems,
  value: cartTotal,
});

Checkout Process

const { trackBeginCheckout, trackShippingInfo, trackPaymentInfo } =
  useGAEcommerce();

// Begin checkout
trackBeginCheckout({
  value: 199.98,
  items: cartItems,
  customParams: {
    checkout_step: 1,
    checkout_option: "guest",
  },
});

// Add shipping info
trackShippingInfo({
  items: cartItems,
  value: 199.98,
  shipping_tier: "standard",
  customParams: {
    shipping_cost: 9.99,
  },
});

// Add payment info
trackPaymentInfo(
  cartItems,
  209.97, // total with shipping
  "credit_card",
  {
    payment_provider: "stripe",
  }
);

Purchase & Refunds

// Track purchase
const { trackPurchase, trackRefund } = useGAEcommerce();

// Track purchase
trackPurchase({
  transactionId: "ORDER_12345",
  value: 209.97,
  items: purchasedItems,
  customParams: {
    affiliation: "Online Store",
    coupon: "SAVE10",
    shipping: 9.99,
    tax: 16.8,
  },
});

// Track refund
trackRefund({
  transactionId: "ORDER_12345",
  value: 99.99,
  items: [refundedItem],
  customParams: {
    refund_reason: "defective_product",
  },
});

Wishlist Tracking

const { trackWishlist } = useGAEcommerce();

// Add to wishlist
trackWishlist({
  action: "add_to_wishlist",
  items: [product],
  value: product.price,
  customParams: {
    wishlist_name: "favorites",
  },
});

// View wishlist
trackWishlist({
  action: "view_wishlist",
  items: wishlistItems,
  value: wishlistTotal,
});

Promotion Tracking

const { trackPromotion } = useGAEcommerce();

// View promotion
trackPromotion({
  action: "view_promotion",
  items: promotionalItems,
  creative_name: "Summer Sale Banner",
  creative_slot: "hero_banner",
  promotion_id: "SUMMER2024",
  promotion_name: "Summer Sale 50% Off",
});

// Select promotion
trackPromotion({
  action: "select_promotion",
  items: promotionalItems,
  creative_name: "Summer Sale Banner",
  creative_slot: "hero_banner",
  promotion_id: "SUMMER2024",
  promotion_name: "Summer Sale 50% Off",
});

Framework Integration

Next.js App Router

// app/layout.tsx
import { GAProvider } from "@connectaryal/google-analytics";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const gaConfig = {
    measurementId: "G-XXXXXXXXXX",
    debug: process.env.NODE_ENV === "development",
    currency: "USD",
  };

  return (
    <html lang="en">
      <body>
        <GAProvider config={gaConfig}>{children}</GAProvider>
      </body>
    </html>
  );
}

Suggestion: Wrap by a custom provider

// app/components/AnalyticsTracker.tsx
"use client";

import { usePathname } from "next/navigation";
import { useGoogleAnalytics } from "@connectaryal/google-analytics";
import { useEffect } from "react";

export function AnalyticsLayout() {
  const pathname = usePathname();
  const { trackPage } = useGoogleAnalytics();

  useEffect(() => {
    trackPage({ path: pathname });
  }, [pathname, trackPage]);

  return null;
}

Next.js Pages Router

// pages/_app.tsx
import type { AppProps } from "next/app";
import { GAProvider } from "@connectaryal/google-analytics";
import { useRouter } from "next/router";
import { useGoogleAnalytics } from "@connectaryal/google-analytics";
import { useEffect } from "react";

function AnalyticsTracker() {
  const router = useRouter();
  const { trackPage } = useGoogleAnalytics();

  useEffect(() => {
    const handleRouteChange = (url: string) => {
      trackPage({ path: url });
    };

    router.events.on("routeChangeComplete", handleRouteChange);
    return () => {
      router.events.off("routeChangeComplete", handleRouteChange);
    };
  }, [router.events, trackPage]);

  return null;
}

export default function App({ Component, pageProps }: AppProps) {
  const gaConfig = {
    measurementId: "G-XXXXXXXXXX",
    debug: process.env.NODE_ENV === "development",
    currency: "USD",
  };

  return (
    <GAProvider config={gaConfig}>
      <AnalyticsTracker />
      <Component {...pageProps} />
    </GAProvider>
  );
}

React Router

import { BrowserRouter, useLocation } from "react-router-dom";
import { GAProvider, useGoogleAnalytics } from "@connectaryal/google-analytics";

function AnalyticsTracker() {
  const location = useLocation();
  const { trackPage } = useGoogleAnalytics();

  useEffect(() => {
    trackPage({ path: location.pathname });
  }, [location, trackPage]);

  return null;
}

function App() {
  return (
    <GAProvider config={{ measurementId: "G-XXXXXXXXXX" }}>
      <BrowserRouter>
        <AnalyticsTracker />
        <Routes>{/* Your routes */}</Routes>
      </BrowserRouter>
    </GAProvider>
  );
}

Google Tag Manager

Pass a GTM container ID as gtmId to load the container with gtm.js. This is Google's official GTM snippet, not the GA4 gtag.js tag. Every hook then pushes its events to window.dataLayer, and your container decides which tags fire.

Migrating? If you were passing GTM-… as measurementId, switch to gtmId. That setup loaded the Google tag instead of your container, never fired gtm.js, and sent a meaningless gtag('config', 'GTM-…'). In v2.0.0, passing a GTM ID as measurementId throws an error.

GTM only: React / Vite

// main.tsx
import { GAProvider } from "@connectaryal/google-analytics";

createRoot(document.getElementById("root")!).render(
  <GAProvider config={{ gtmId: "GTM-XXXXXXX" }}>
    <App />
  </GAProvider>
);

In a client-only SPA, GTMNoScript can't help, because the page never renders without JavaScript. To support no-JS visitors, paste Google's <noscript> iframe into index.html right after <body>.

GTM only: Next.js App Router

// app/layout.tsx
import { GAProvider, GTMNoScript } from "@connectaryal/google-analytics";

const GTM_ID = process.env.NEXT_PUBLIC_GTM_ID!; // "GTM-XXXXXXX"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {/* Must come right after the opening <body> tag */}
        <GTMNoScript gtmId={GTM_ID} />
        <GAProvider config={{ gtmId: GTM_ID }}>{children}</GAProvider>
      </body>
    </html>
  );
}

GAProvider is a client component. Nothing touches window or document during server rendering, and gtm.js is injected after hydration. GTMNoScript renders:

<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>

It accepts gtmAuth and gtmPreview for GTM environments, and renders nothing if gtmId isn't a valid GTM-XXXXXXX ID.

GTM options

<GAProvider
  config={{
    gtmId: "GTM-XXXXXXX",
    dataLayerName: "dataLayer", // custom name adds &l=<name> to gtm.js
    gtmAuth: "aBcD123", // GTM environment: set both gtmAuth and gtmPreview
    gtmPreview: "env-3",
    nonce: cspNonce, // added to injected <script> tags
    customConfig: {
      // Consent Mode v2 defaults, pushed before gtm.js loads
      ad_storage: "denied",
      ad_user_data: "denied",
      ad_personalization: "denied",
      analytics_storage: "denied",
    },
  }}
>

What happens on init():

  1. window[dataLayerName] is created if missing.
  2. If customConfig is set, gtag("consent", "default", customConfig) is pushed as an arguments object, which is the form GTM's Consent Mode expects.
  3. { "gtm.start": Date.now(), event: "gtm.js" } is pushed.
  4. https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX is inserted as an async script.

If a gtm.js script for the same container is already on the page, for example from a snippet in index.html, it isn't injected again and gtm.js isn't re-fired. In that case, put your consent defaults in the page before that snippet.

What gets pushed to the dataLayer

| Call | dataLayer push | | --------------------------------- | --------------------------------------------------------------------------------------- | | trackCustomEvent({ name, params }) | { event: name, ...params } | | trackPage() | { event: "page_view", page_path, page_title, page_location, page_referrer } | | Every useGAEcommerce event | { ecommerce: null }, then { event: name, ecommerce: { currency, value, items, … } } | | trackLogin / trackSignUp | { event: "login", method } / { event: "sign_up", method } | | setUserId(id) | { user_id: id } | | setUserProperties(props) | { user_properties: props } |

Ecommerce events follow Google's GA4 GTM ecommerce format. The { ecommerce: null } push stops the previous event's items from leaking into the next one.

GTM's data model keeps values from earlier pushes. A Data Layer Variable such as value still holds the last value that was pushed, even on a later event that didn't set it. Read event-specific values only in tags triggered by that event.

Setting up tags in the GTM UI

1. Google tag (GA4 base). Tags → New → Google Tag. Tag ID: G-XXXXXXXXXX. Trigger: Initialization - All Pages. If you send page views yourself with trackPage(), add the configuration parameter send_page_view = false so page views aren't counted twice. To attach the user ID, add the configuration parameter user_id = {{DLV - user_id}}.

2. Data Layer Variables. Variables → New → Data Layer Variable, one for each parameter you want to read, for example page_location, page_title, method, user_id or user_properties.plan. Use Data Layer Version 2.

3. Custom event tags. Triggers → New → Custom Event, with the event name cta_click. Tags → New → Google Analytics: GA4 Event. Measurement ID: G-XXXXXXXXXX. Event name: {{Event}}. Add event parameters mapped to your Data Layer Variables, and attach the trigger.

4. Page views. Add a Custom Event trigger for page_view. Then add a GA4 Event tag with the event name page_view and the parameters page_location, page_title and page_referrer, mapped to their Data Layer Variables.

5. Ecommerce. Add a Custom Event trigger with Use regex matching turned on:

^(view_item_list|select_item|view_item|add_to_cart|remove_from_cart|view_cart|begin_checkout|add_shipping_info|add_payment_info|purchase|refund|view_promotion|select_promotion)$

Then add a GA4 Event tag with the event name {{Event}}. Under More Settings → Ecommerce, turn on Send Ecommerce data and set Data source to Data Layer. You don't need any item or value variables, because the tag reads the ecommerce object directly.

Use Preview (Tag Assistant) to confirm each push fires the expected tag, then publish the container.

GA4 + GTM together

Set both IDs to load gtag.js and gtm.js on the same dataLayer. For example, you might send GA4 through gtag while GTM manages ad and marketing tags.

<GAProvider
  config={{
    measurementId: "G-XXXXXXXXXX",
    gtmId: "GTM-XXXXXXX",
    eventTransport: "gtag", // "gtag" | "dataLayer" | "both"
  }}
>

| eventTransport | Events go to | Use when | | ------------------------------ | ------------------------------------------------ | -------------------------------------------------------------------- | | "dataLayer" (default with gtmId) | window.dataLayer only | Your GTM container sends the GA4 events | | "gtag" (default without gtmId) | window.gtag("event", …) only | gtag.js sends GA4, and GTM only handles other tags | | "both" | Both | Other GTM tags (ads, pixels) need the same events that gtag sends to GA4 |

⚠️ Double counting. When measurementId is set, gtag.js already sends GA4 hits for that property. If your GTM container also has a Google tag or GA4 Event tags for the same measurement ID, every page view and event is counted twice. Use one path per GA4 property. With debug: true, the library warns when both IDs are set.


Advanced Configuration

Complete Configuration Options

const gaConfig = {
  measurementId: "G-XXXXXXXXXX",
  debug: process.env.NODE_ENV === "development",
  currency: "USD",
  disableGA: false,
  customConfig: {
    // GDPR/Privacy compliance
    analytics_storage: "granted",
    ad_storage: "denied",
    ad_user_data: "denied",
    ad_personalization: "denied",

    // Custom settings
    send_page_view: false, // Disable automatic page views
    custom_map: {
      custom_parameter_1: "user_type",
    },
  },
};

<GAProvider config={gaConfig}>
  <App />
</GAProvider>;

Environment Variables

# .env.local (Next.js)
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX

# .env (Create React App)
REACT_APP_GA_MEASUREMENT_ID=G-XXXXXXXXXX

Conditional Loading

function App() {
  // Only load analytics in production
  const gaConfig = {
    measurementId: "G-XXXXXXXXXX",
    currency: "USD",
    disableGA: process.env.NODE_ENV === "development", // true will disable google analytics tracking
  };

  return (
    <GAProvider config={gaConfig}>
      <YourApp />
    </GAProvider>
  );
}

Real-World Examples

E-commerce Product Card

function ProductCard({ product }) {
  const { trackItem, trackCart } = useGAEcommerce();

  const handleView = () => {
    trackItem({
      action: "view_item",
      items: [
        {
          item_id: product.sku,
          item_name: product.name,
          item_category: product.category,
          item_brand: product.brand,
          price: product.price,
          quantity: 1,
        },
      ],
      value: product.price,
    });
  };

  const handleAddToCart = () => {
    trackCart({
      action: "add_to_cart",
      items: [
        {
          item_id: product.sku,
          item_name: product.name,
          price: product.price,
          quantity: 1,
        },
      ],
      value: product.price,
      customParams: {
        product_location: "product_grid",
      },
    });
  };

  return (
    <div onClick={handleView} className="product-card">
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <button onClick={handleAddToCart}>Add to Cart</button>
    </div>
  );
}

Newsletter Signup Form

function NewsletterForm() {
  const { trackCustomEvent, trackException } = useGoogleAnalytics();
  const [email, setEmail] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();

    try {
      await subscribeToNewsletter(email);

      // Track successful signup
      trackCustomEvent({
        name: "newsletter_signup",
        params: {
          method: "email",
          success: true,
          source: "footer_form",
        },
      });
    } catch (error) {
      // Track failed signup
      trackException({
        description: "Newsletter signup failed",
        fatal: false,
        customParams: {
          error_type: "subscription_error",
        },
      });
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Enter your email"
      />
      <button type="submit">Subscribe</button>
    </form>
  );
}

User Authentication

function LoginForm() {
  const { trackLogin, trackException } = useGoogleAnalytics();

  const handleLogin = async (method) => {
    try {
      const user = await signIn(method);

      trackLogin({
        method: method,
        customParams: {
          user_id: user.id,
          user_type: user.isNew ? "new" : "returning",
        },
      });
    } catch (error) {
      trackException({
        description: `Login failed: ${method}`,
        fatal: false,
      });
    }
  };

  return (
    <div>
      <button onClick={() => handleLogin("google")}>Login with Google</button>
      <button onClick={() => handleLogin("email")}>Login with Email</button>
    </div>
  );
}

Video Player Integration

function VideoPlayer({ video }) {
  const { trackVideoPlay, trackCustomEvent } = useGoogleAnalytics();
  const [hasStarted, setHasStarted] = useState(false);

  const handlePlay = () => {
    if (!hasStarted) {
      trackVideoPlay({
        title: video.title,
        url: video.url,
        duration: video.duration,
        customParams: {
          video_category: video.category,
          video_quality: "1080p",
        },
      });
      setHasStarted(true);
    }
  };

  const handleProgress = (currentTime) => {
    const percent = Math.round((currentTime / video.duration) * 100);

    // Track video progress at 25%, 50%, 75%
    if ([25, 50, 75].includes(percent)) {
      trackCustomEvent({
        name: "video_progress",
        params: {
          video_title: video.title,
          video_percent: percent,
          video_current_time: currentTime,
        },
      });
    }
  };

  return (
    <video
      onPlay={handlePlay}
      onTimeUpdate={(e) => handleProgress(e.target.currentTime)}
    >
      <source src={video.url} type="video/mp4" />
    </video>
  );
}

TypeScript Support

Full TypeScript support with intelligent autocomplete:

import type {
  GA4Config,
  GAEvent,
  CartItem,
  EcommerceItem,
  PageViewOptions,
} from "@connectaryal/google-analytics";

// Configuration is fully typed
const config: GA4Config = {
  measurementId: "G-XXXXXXXXXX",
  debug: true,
  currency: "USD", // IntelliSense suggests valid currencies
};

// All parameters are validated at compile time
const { trackCustomEvent } = useGoogleAnalytics();

trackCustomEvent({
  name: "purchase",
  params: {
    value: 99.99, // ✅ number
    currency: "USD", // ✅ valid currency
    items: [], // ✅ EcommerceItem[]
    // invalid_param: 123 // ❌ TypeScript error
  },
});

// Cart items are strongly typed
const cartItem: CartItem = {
  item_id: "SKU123", // ✅ required
  item_name: "Product", // ✅ required
  price: 29.99, // ✅ required
  quantity: 2, // ✅ optional, defaults to 1
  item_category: "Electronics", // ✅ optional
};

API Reference

Core Tracking Methods

| Method | Description | Parameters | | ----------------------- | ------------------------ | ------------------------------------------------- | | trackPage() | Track page views | PageViewOptions? | | trackCustomEvent() | Track custom events | {name, params?, category?} | | trackSearch() | Track site search | {searchTerm, options?} | | trackEngagement() | Track user engagement | {type, options?} | | trackShare() | Track social sharing | {contentType, contentId, method, customParams?} | | trackVideoPlay() | Track video interactions | {title, url, duration?, customParams?} | | trackFormSubmission() | Track form submissions | {formId, formData, customParams?} | | trackException() | Track errors/exceptions | {description, fatal?, customParams?} | | trackTiming() | Track performance timing | (category, variable, value, label?) |

Core Tracking Methods

| Method | Description | Parameters | | --------------- | ----------------------- | ------------------------- | | trackLogin() | Track user login | {method, customParams?} | | trackSignUp() | Track user registration | {method, customParams?} |

Ecommerce Methods

| Method | Description | Use Cases | | ---------------------- | ---------------------- | -------------------------------------- | | trackItem() | Item interactions | View item, select item, view item list | | trackCart() | Cart operations | Add/remove/view/update cart | | trackWishlist() | Wishlist operations | Add/remove/view/update wishlist | | trackBeginCheckout() | Checkout initiation | Start checkout process | | trackShippingInfo() | Shipping selection | Add shipping information | | trackPaymentInfo() | Payment selection | Add payment information | | trackPurchase() | Purchase completion | Order confirmation | | trackRefund() | Purchase refund | Process refunds | | trackPromotion() | Promotion interactions | View/select promotions |

Configuration Options

interface GAConfig {
  measurementId?: string; // GA4 Measurement ID (G-XXXXXXXXXX), loaded via gtag.js
  gtmId?: string; // GTM container ID (GTM-XXXXXXX), loaded via gtm.js
  dataLayerName?: string; // Default "dataLayer"
  gtmAuth?: string; // GTM environment gtm_auth (requires gtmPreview)
  gtmPreview?: string; // GTM environment gtm_preview, e.g. "env-3"
  nonce?: string; // CSP nonce for injected scripts
  eventTransport?: "gtag" | "dataLayer" | "both"; // Default: "dataLayer" with gtmId, else "gtag"
  debug?: boolean; // Enable debug logging
  currency?: Currency; // Default currency (USD, EUR, etc.)
  customConfig?: Record<string, unknown>; // Consent Mode defaults: gtag("consent", "default", …)
  disableGA?: boolean; // Enable/Disable tracking
}

// `GA4Config` is still exported as an alias of `GAConfig`.
// At least one of measurementId / gtmId is required unless disableGA is true.

Auth & User Methods (useGAAuth)

| Method | Description | Parameters | | --------------------- | ---------------------------- | ------------------------- | | setUserId() | Set or clear the GA4 user ID | string \| null | | setUserProperties() | Set GA4 user properties | Record<string, string \| number \| boolean \| null> |

Utilities

| Export | Description | | -------------------------------------- | --------------------------------------------------- | | GTMNoScript | <noscript> iframe fallback for GTM | | isValidGtmId() / isValidMeasurementId() | ID format validation | | getDataLayer(name?) | Returns window[name] if it's an array (SSR-safe) | | buildGtmScriptUrl() / buildGtmNoScriptUrl() | Build the official GTM URLs |


Performance Optimization

The library includes several performance optimizations:

  • Event Batching: Multiple events are batched together to reduce network calls
  • Memory Leak Prevention: Proper cleanup of event listeners and timers
  • Lazy Script Loading: GA script is loaded asynchronously when needed
  • Initialization Caching: Prevents multiple initialization attempts
  • Error Boundaries: Graceful error handling prevents crashes

Debugging & Development

Debug Mode

Enable debug mode to see detailed logging:

<GAProvider config={{
  measurementId: "G-XXXXXXXXXX",
  debug: process.env.NODE_ENV === "development"
}}>

Debug output examples:

GA4: Initializing with measurement ID: G-XXXXXXXXXX
GA4: Tracking event "page_view" with params: { page_title: "Home", page_location: "/" }
GA4: Event sent successfully ✅
GA4: Warning: Search term is required for search events ⚠️

Common Issues & Solutions

Issue: Events not showing in GA4

// ✅ Ensure provider is properly configured
<GAProvider config={{ measurementId: "G-XXXXXXXXXX" }}>

// ✅ Check debug mode for errors
config={{ debug: true }}

Issue: TypeScript errors

// ✅ Import types explicitly
import type { CartItem } from "@connectaryal/google-analytics";

// ✅ Use proper parameter types
ga.trackCustomEvent({
  name: "event_name",
  params: { key: "value" }, // Must be Record<string, unknown>
});

Migration Guide

From react-ga4

// Before (react-ga4)
import ReactGA from "react-ga4";
ReactGA.initialize("G-XXXXXXXXXX");
ReactGA.send("pageview");

// After (@connectaryal/google-analytics)
import { GAProvider, useGoogleAnalytics } from "@connectaryal/google-analytics";

<GAProvider config={{ measurementId: "G-XXXXXXXXXX" }}>
  <App />
</GAProvider>;

const { trackPage } = useGoogleAnalytics();
trackPage();

From measurementId: "GTM-…" (v1.1 and earlier)

If you were passing GTM-… as measurementId, switch to gtmId:

- <GAProvider config={{ measurementId: "GTM-XXXXXXX" }}>
+ <GAProvider config={{ gtmId: "GTM-XXXXXXX" }}>

Events now go to window.dataLayer, so you'll need GTM triggers and tags to forward them to GA4. See Setting up tags in the GTM UI. Add <GTMNoScript> after <body> for no-JS visitors.

From gtag directly

// Before (gtag)
gtag('event', 'purchase', {
  transaction_id: '12345',
  value: 25.42,
  currency: 'USD'
});

// After (@connectaryal/google-analytics)
const { trackPurchase } = useGAEcommerce();
trackPurchase({
  transactionId: '12345',
  value: 25.42,
  items: [...]
});

Browser Support

  • Chrome 60+
  • Firefox 55+
  • Safari 12+
  • Edge 79+

Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

git clone https://github.com/connectaryal/google-analytics.git
cd google-analytics
npm install
npm run dev

Running Tests

yarn test        # vitest + jsdom
yarn type-check

License

MIT © Shiva Aryal


Support & Resources

Star this repo if it helped you! ⭐


Made with ❤️ for the React community