@technoapple/ga4
v1.1.1
Published
TypeScript Node.js library to support GA4 analytics.
Downloads
100
Maintainers
Readme
@technoapple/ga4
A lightweight TypeScript GA4 helper for browser apps.
It provides:
- A simple GA4 wrapper (
init,send, directgtagaccess) - A
dataLayerhelper (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/ga4Quick 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.
getLastomitted orfalse: returns first matching valuegetLastset totrue: 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_urllink_domainoutbound: 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_actionform_domainoutbound: 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: falsesessionTimeout: 30(minutes)eventName: 'page_visibility'
Default params:
visibility_statevisibility_durationpage_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_pathpage_titlepage_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_locationpage_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:
stripQueryqueryParamsAllowlistqueryParamsDenylisttrailingSlash: '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: 1000eventName: 'media_query_change'
Default params:
media_query_namemedia_query_valuemedia_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:
EventTrackerOptionsOutboundLinkTrackerOptionsOutboundFormTrackerOptionsPageVisibilityTrackerOptionsUrlChangeTrackerOptionsImpressionTrackerOptionsCleanUrlTrackerOptionsMediaQueryTrackerOptions
Development
npm run build
npm test
npm run test:coverageNotes
- 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()orga4.removeAll()to avoid duplicate listeners in long-lived apps.
License
ISC
