Skip to content

Google Analytics 4 (GA4)

Google Analytics 4 is used for web analytics and conversion tracking across web applications.

AppIntegrated
UK
DE

GA4 can be integrated via Google tag (gtag.js) API. An overview of the integration methods can be found via the links below:

An overview of the debugging tools can be found via the links below:

Before integrating GA4, note the following:

  • Default consent should be configured before GA4 loads.
  • Event names and parameters should follow a consistent schema of the recommended events.

The @releafuk/analytics package provides a custom GA4 plugin through the ga4Provider() factory function. GA4 is integrated directly through gtag.js, without requiring GTM.

The ga4Provider() factory accepts the config parameter that should satisfy the Ga4Config type:

type GtagConfig = {
debug_mode?: boolean;
send_page_view?: boolean;
};
type Ga4Config = {
measurementId: string;
enabled?: boolean;
nonce?: string;
gtagName?: string;
dataLayerName?: string;
gtagConfig?: GtagConfig;
};
OptionRequiredDefaultDescription
measurementIdYes-GA4 measurement ID
enabledNotrueControls whether the provider loads and initializes GA4. Use false to keep the provider registered but disabled for an environment.
nonceNo-CSP nonce added to the dynamically loaded Google tag script
gtagNameNogtagGlobal Google tag function name
dataLayerNameNodataLayerGlobal data layer name.
gtagConfigNoSee belowGoogle tag configuration

The gtagConfig object is passed to the GA4 gtag("config", measurementId, gtagConfig) command during provider initialization. See the Google documentation for the GA4 configuration fields and page-view measurement.

OptionDefaultDescription
debug_modefalseMarks events for GA4 DebugView. The provider also enables this automatically when analytics service debug mode is enabled.
send_page_viewtrueControls whether GA4 sends a page_view event automatically when the provider runs the config command.

The send_page_view option determines how page views are handled:

  • send_page_view: true uses GA4 automatic page-view tracking. The custom GA4 page() implementation does not send another event when analytics.page() is called.
  • send_page_view: false disables the automatic page view sent by the GA4 config command. The custom GA4 page() implementation runs when analytics.page() is called and sends a page_view event with page_title, page_location, page_path, page_hash, page_search, and page_referrer.

Use send_page_view: false when the application controls navigation or needs to send page views manually, for example in a single-page application. Disabling the automatic page view avoids duplicate page_view events.

ga4Provider({
measurementId: import.meta.env.PUBLIC_GA4_MEASUREMENT_ID,
gtagConfig: {
debug_mode: import.meta.env.ENVIRONMENT === "development",
send_page_view: false,
},
});

Define GA4 event parameters under the ga4 provider key, then register ga4Provider() when creating the analytics service:

import {
createAnalyticsService,
createEvent,
createEventDefinition,
createEventRegistry,
ga4Provider,
} from "@releafuk/analytics";
const purchase = createEvent<
{
orderId: string;
total: number;
currency: string;
items: Array<{
productId: string;
name: string;
}>;
},
"purchase"
>({
name: "purchase",
});
const events = createEventRegistry().register(
createEventDefinition(purchase, {
providers: {
ga4: (data) => ({
name: "purchase",
items: data.items.map((item) => ({
item_id: item.productId,
item_name: item.name,
})),
currency: data.currency,
value: data.total,
transaction_id: data.orderId,
}),
},
}),
);
export const analytics = createAnalyticsService({
app: "releaf",
events,
providers: [
ga4Provider({
measurementId: import.meta.env.PUBLIC_GA4_MEASUREMENT_ID,
}),
],
consent: {
default: {
analytics: "denied",
advertising: "denied",
functionality: "denied",
adUserData: "denied",
adPersonalization: "denied",
},
},
});
await analytics.init();

Render the provider markup in the document head so the default consent command runs before GA4 is initialized. For example, in an Astro layout:

---
import { analytics } from "@/libs/analytics";
---
<head>
{
analytics.markup?.().map((contribution) => {
if (contribution.kind === "inline-script") {
return (
<script
is:inline
id={contribution.id}
set:html={contribution.content}
transition:persist
/>
);
}
})
}
</head>