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

@technoapple/ga4

v1.1.1

Published

TypeScript Node.js library to support GA4 analytics.

Downloads

100

Readme

@technoapple/ga4

A lightweight TypeScript GA4 helper for browser apps.

It provides:

  • A simple GA4 wrapper (init, send, direct gtag access)
  • A dataLayer helper (dataLayerHelper.get)
  • Opt-in automatic tracking plugins (event delegation, outbound tracking, SPA page views, impression tracking, URL cleanup, media query tracking)

Why this library

GA4 and gtag.js are flexible, but many teams still need reusable browser-side utilities:

  • Keep initialization and event sending consistent
  • Add automatic tracking without wiring listeners repeatedly
  • Keep plugin logic tree-shakeable and removable

Installation

npm install @technoapple/ga4

Quick Start

import { ga4 } from '@technoapple/ga4';

ga4.init({ targetId: 'G-XXXXXXX' });

ga4.send('sign_up', {
    method: 'email',
    plan: 'pro',
});

Framework Examples

Use these integration guides for copy-paste setup patterns and lifecycle notes:

API

ga4.init(option)

Initializes window.dataLayer and window.gtag, then sends:

  • gtag('js', new Date())
  • gtag('config', option.targetId)
ga4.init({ targetId: 'G-XXXXXXX' });

ga4.send(eventName, eventParameters)

Sends a GA4 event through gtag('event', ...).

ga4.send('purchase', {
    transaction_id: 'order_123',
    value: 99,
    currency: 'USD',
});

ga4.gtag

Direct access to the typed gtag function.

ga4.gtag('event', 'login', { method: 'google' });

ga4.use(PluginClass, options?)

Registers a plugin and returns its instance.

import { ga4, OutboundLinkTracker } from '@technoapple/ga4';

const tracker = ga4.use(OutboundLinkTracker, {
    eventName: 'outbound_link_click',
});

// Later
tracker.remove();

ga4.removeAll()

Unregisters all plugins and calls each plugin's remove().

dataLayerHelper.get(key, getLast?)

Reads values from window.dataLayer.

  • getLast omitted or false: returns first matching value
  • getLast set to true: returns last matching value
import { dataLayerHelper } from '@technoapple/ga4';

const firstValue = dataLayerHelper.get('campaign');
const latestValue = dataLayerHelper.get('campaign', true);

Plugin Catalog

All plugins implement:

interface GA4Plugin {
    remove(): void;
}

EventTracker

Declarative DOM tracking via attributes (default prefix: data-ga4-).

Options matrix:

| Option | Type | Default | Notes | |---|---|---|---| | events | string[] | ['click'] | DOM events to listen to via delegation | | attributePrefix | string | 'data-ga4-' | Reads attributes like data-ga4-on and data-ga4-event-name | | hitFilter | (params, element, event) => Record<string, unknown> \| null | undefined | Return null to skip sending |

Defaults:

  • events: ['click']
  • attributePrefix: 'data-ga4-'

Example:

<button
    data-ga4-on="click"
    data-ga4-event-name="video_play"
    data-ga4-video-title="Summer Launch">
    Play
</button>
import { ga4, EventTracker } from '@technoapple/ga4';

ga4.use(EventTracker);

OutboundLinkTracker

Tracks clicks on links whose hostname differs from location.hostname.

Options matrix:

| Option | Type | Default | Notes | |---|---|---|---| | events | string[] | ['click'] | Event types for delegated tracking | | linkSelector | string | 'a, area' | CSS selector for trackable links | | shouldTrackOutboundLink | (link, parseUrl) => boolean | Built-in external-hostname matcher | Override outbound detection logic | | eventName | string | 'outbound_link_click' | Custom event name | | hitFilter | (params, element, event) => Record<string, unknown> \| null | undefined | Return null to cancel event |

Defaults:

  • events: ['click']
  • linkSelector: 'a, area'
  • eventName: 'outbound_link_click'

Default params:

  • link_url
  • link_domain
  • outbound: true

OutboundFormTracker

Tracks form submissions whose form.action points to an external hostname.

Options matrix:

| Option | Type | Default | Notes | |---|---|---|---| | formSelector | string | 'form' | CSS selector for forms to observe | | shouldTrackOutboundForm | (form, parseUrl) => boolean | Built-in external-hostname matcher | Override outbound detection logic | | eventName | string | 'outbound_form_submit' | Custom event name | | hitFilter | (params, element, event) => Record<string, unknown> \| null | undefined | Return null to cancel event |

Defaults:

  • formSelector: 'form'
  • eventName: 'outbound_form_submit'

Default params:

  • form_action
  • form_domain
  • outbound: true

PageVisibilityTracker

Tracks visible/hidden durations using document.visibilityState.

Options matrix:

| Option | Type | Default | Notes | |---|---|---|---| | sendInitialPageview | boolean | false | Sends initial page_view when page is visible | | sessionTimeout | number | 30 | Minutes before visible return triggers new page_view | | eventName | string | 'page_visibility' | Custom visibility event name | | hitFilter | (params) => Record<string, unknown> \| null | undefined | Return null to skip event |

Defaults:

  • sendInitialPageview: false
  • sessionTimeout: 30 (minutes)
  • eventName: 'page_visibility'

Default params:

  • visibility_state
  • visibility_duration
  • page_path

UrlChangeTracker

Tracks SPA navigation by patching history.pushState, optional replaceState, and listening to popstate.

Options matrix:

| Option | Type | Default | Notes | |---|---|---|---| | shouldTrackUrlChange | (newPath, oldPath) => boolean | newPath !== oldPath | Decide when to emit page_view | | trackReplaceState | boolean | false | Also patch history.replaceState | | hitFilter | (params) => Record<string, unknown> \| null | undefined | Return null to skip event |

Defaults:

  • trackReplaceState: false

Sends page_view with:

  • page_path
  • page_title
  • page_location

ImpressionTracker

Tracks element impressions with IntersectionObserver and dynamic DOM changes via MutationObserver.

Options matrix:

| Option | Type | Default | Notes | |---|---|---|---| | elements | Array<string \| ImpressionElementConfig> | [] | Element IDs or config objects to observe | | rootMargin | string | '0px' | Passed to IntersectionObserver | | attributePrefix | string | 'data-ga4-' | Reads matching element attributes into params | | eventName | string | 'element_impression' | Custom event name | | hitFilter | (params, element) => Record<string, unknown> \| null | undefined | Return null to skip event |

Defaults:

  • rootMargin: '0px'
  • attributePrefix: 'data-ga4-'
  • eventName: 'element_impression'

Can observe with element IDs or configs:

import { ga4, ImpressionTracker } from '@technoapple/ga4';

ga4.use(ImpressionTracker, {
    elements: [
        'hero-banner',
        { id: 'cta-block', threshold: 0.5, trackFirstImpressionOnly: true },
    ],
});

CleanUrlTracker

Intercepts gtag calls for config and page_view payloads and normalizes:

  • page_location
  • page_path

Options matrix:

| Option | Type | Default | Notes | |---|---|---|---| | stripQuery | boolean | false | Remove all query params unless allowlist is set | | queryParamsAllowlist | string[] | undefined | Keep only selected params when stripping query | | queryParamsDenylist | string[] | undefined | Remove selected params when not stripping query | | trailingSlash | 'add' \| 'remove' | undefined | Normalize page_path slash behavior | | urlFilter | (url) => string | undefined | Final custom URL transform |

Options include:

  • stripQuery
  • queryParamsAllowlist
  • queryParamsDenylist
  • trailingSlash: 'add' | 'remove'
  • urlFilter

MediaQueryTracker

Tracks responsive breakpoint changes using matchMedia.

Options matrix:

| Option | Type | Default | Notes | |---|---|---|---| | definitions | MediaQueryDefinition[] | [] | Named breakpoint sets to track | | changeTemplate | (oldValue, newValue) => string | ${oldValue} => ${newValue} | Label formatter for change payload | | changeTimeout | number | 1000 | Debounce delay in ms | | eventName | string | 'media_query_change' | Custom event name | | hitFilter | (params) => Record<string, unknown> \| null | undefined | Return null to skip event |

Defaults:

  • changeTimeout: 1000
  • eventName: 'media_query_change'

Default params:

  • media_query_name
  • media_query_value
  • media_query_change

Plugin Lifecycle

Use either approach:

const plugin = ga4.use(EventTracker);
plugin.remove();
ga4.removeAll();

Full Example

import {
    ga4,
    EventTracker,
    OutboundLinkTracker,
    UrlChangeTracker,
    CleanUrlTracker,
} from '@technoapple/ga4';

ga4.init({ targetId: 'G-XXXXXXX' });

ga4.use(CleanUrlTracker, {
    stripQuery: true,
    queryParamsAllowlist: ['utm_source', 'utm_medium', 'utm_campaign'],
    trailingSlash: 'remove',
});

ga4.use(EventTracker, { events: ['click', 'submit'] });
ga4.use(OutboundLinkTracker);
ga4.use(UrlChangeTracker, { trackReplaceState: true });

ga4.send('app_initialized', { env: 'production' });

TypeScript Support

The package ships type definitions and exports plugin option types, including:

  • EventTrackerOptions
  • OutboundLinkTrackerOptions
  • OutboundFormTrackerOptions
  • PageVisibilityTrackerOptions
  • UrlChangeTrackerOptions
  • ImpressionTrackerOptions
  • CleanUrlTrackerOptions
  • MediaQueryTrackerOptions

Development

npm run build
npm test
npm run test:coverage

Notes

  • Browser-focused library: APIs rely on window, document, and browser events.
  • Call ga4.init(...) before sending events or registering plugins.
  • If you register many plugins, clean them up with remove() or ga4.removeAll() to avoid duplicate listeners in long-lived apps.

License

ISC