@amboras-dev/ga4
v0.3.1
Published
Google Analytics 4 (gtag.js + Measurement Protocol) hooks for Amboras storefronts
Downloads
306
Keywords
Readme
@amboras-dev/ga4
Google Analytics 4 for Amboras storefronts. Hybrid transport: gtag.js fires from the browser AND the same events relay through your store server via Measurement Protocol, so ad-blocker traffic still lands.
Install
pnpm add @amboras-dev/ga4Then in the storefront's app/_generated/plugin-registry.ts (the orchestrator
regenerates this on every install — manual edits will be overwritten):
import {
GA4Provider,
GA4ViewItem,
GA4ViewItemList,
GA4CartDelta,
GA4ViewCart,
GA4BeginCheckout,
GA4AddPaymentInfo,
GA4Purchase,
GA4Identify,
GA4Search,
} from '@amboras-dev/ga4/slot-components'
export const PLUGIN_REGISTRY = {
rootProviders: [
{ id: 'ga4', Component: GA4Provider, propsFromConfig: { measurementId: 'measurement_id', debugMode: 'debug_mode' } },
],
pdpAnalytics: [{ id: 'ga4-view-item', Component: GA4ViewItem, propsFromContext: ['product', 'currency'] }],
collectionAboveGrid: [{ id: 'ga4-view-item-list', Component: GA4ViewItemList, propsFromContext: ['items', 'listName', 'currency'] }],
cartUpdate: [{ id: 'ga4-cart-delta', Component: GA4CartDelta, propsFromContext: ['previous', 'current', 'currency'] }],
checkoutOrderSummary: [{ id: 'ga4-view-cart', Component: GA4ViewCart, propsFromContext: ['items', 'currency', 'value'] }],
checkoutStart: [
{ id: 'ga4-begin-checkout', Component: GA4BeginCheckout, propsFromContext: ['items', 'currency', 'value'] },
{ id: 'ga4-add-payment-info', Component: GA4AddPaymentInfo, propsFromContext: ['items', 'currency', 'value', 'paymentType'] },
],
// checkoutComplete: pass `items` (real PurchaseLineItem[]) — not just itemCount —
// so merchandising reports get real item_id / item_name / price / quantity.
checkoutComplete: [{ id: 'ga4-purchase', Component: GA4Purchase, propsFromContext: ['orderId', 'total', 'currency', 'items', 'tax', 'shipping', 'coupon'] }],
searchAboveResults: [{ id: 'ga4-search', Component: GA4Search, propsFromContext: ['searchTerm', 'resultsCount'] }],
accountOverview: [{ id: 'ga4-identify', Component: GA4Identify, propsFromContext: ['userId', 'email'] }],
// ...other slots empty
}What's tracked
| Stage | Event |
|---|---|
| Browsing | page_view |
| Discovery | view_item_list, view_item, search |
| Intent | add_to_cart, remove_from_cart, view_cart |
| Checkout | begin_checkout, add_payment_info |
| Conversion | purchase (real items[] + transaction_id, dedupped via event_id) |
| Identity | login |
UTM attribution (utm_source, utm_medium, utm_campaign, gclid, fbclid)
is captured on first visit and attached to every event so revenue attribution
survives multi-page sessions.
Server-side relay
The /store/ga4/collect Medusa route proxies events to Google's Measurement
Protocol with the merchant's API secret (stored in store.metadata.plugins.ga4,
never exposed to the browser). This is what makes ad-blocker traffic land —
when uBlock strips googletagmanager.com, MP still delivers.
The sendMpEvent() helper reads NEXT_PUBLIC_MEDUSA_BACKEND_URL +
NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY + NEXT_PUBLIC_STORE_ID from the
environment by default. For multi-store templates that resolve config at
runtime, call configureGa4Transport({ baseUrl, publishableKey, storeId })
once during app boot.
Consent
The provider gates gtag init on the amboras_consent cookie's analytics
field (defaults to consent-granted when no cookie is set). Re-checks on the
amboras-consent-changed event so it initializes the moment a merchant
accepts, without a page reload.
Programmatic API
For ad-hoc events outside the slot system:
import { trackPurchase, trackViewItem, trackSearch, sendMpEvent } from '@amboras-dev/ga4'
trackPurchase('order_123', { value: 49.99, currency: 'USD', items: [...] })sendMpEvent is the lower-level escape hatch — pass any GA4 event shape.
