Send one event from your React app and it reaches Google Tag Manager, Google Analytics 4 and the Meta Pixel at once. The library keeps the visitor's privacy choices, remembers which campaign brought them, strips personal data out of events, and can send the same events again from your server so they still arrive when a browser blocks tracking.
Try it in the playground: press a button and see exactly what each of those services would receive. Nothing is sent anywhere.
Setting this up with an AI assistant? There's a prompt written for that: Set this up with an AI assistant. It interviews you, writes the wiring, and tells you what to click in each service.
Coming from 0.4? 1.0 is a rewrite, and the API is different. The migration guide maps the old names to the new ones, and the changelog lists everything that changed.
- What you need before you start
- Install
- Set this up with an AI assistant
- Quick start
- Track clicks without writing code
- Follow a multi-step flow
- Tell the library who the visitor is
- Record what the visitor consented to
- Recognise a returning visitor
- Report how fast your pages are
- Send events from your server
- Use it without React
- What each service receives
- Documentation
- License
This library sends events to accounts you already have. It doesn't create or configure them for you, and it isn't a replacement for them. Before you install it, set up the ones you want to use:
| You want | What you need first | What the library needs from it |
|---|---|---|
| Google Tag Manager | A container, with the tags and triggers that decide what happens to each event | The container ID, like GTM-XXXXXXX |
| Google Analytics 4 | A property with a web data stream | The measurement ID, like G-XXXXXXX |
| Meta Pixel | A dataset (pixel) in Meta Events Manager | The pixel ID, a long number |
| Events sent from your server to GA4 | An API secret on that same data stream | The secret, kept on your server |
| Events sent from your server to Meta | A Conversions API access token in Events Manager | The token, kept on your server |
Two things worth knowing:
- With Tag Manager, your container still decides what happens. The library puts each event into the dataLayer, the list of events Tag Manager watches. Until you add a trigger and a tag for an event name, the event is recorded but goes nowhere.
- With Google Analytics 4, events arrive on their own. They show up as events with the name you tracked. Using the
names Google recommends, such as
purchaseorsign_up, fills in GA4's built-in reports; your own names appear in reports and explorations once you use them.
You only configure the services you use. Many teams start with Tag Manager alone and add the rest later.
npm install react-marketing-toolsWorks with React 18 and 19. React is an optional peer: only the main react-marketing-tools entry uses it, so a
backend that imports just react-marketing-tools/server or react-marketing-tools/core doesn't install it. The package is published as ES modules, the import style of JavaScript, so it works in
every current bundler and in Node.js 22.12 or later; it can't be loaded with require().
There's a prompt you can paste into any AI coding assistant. It reads your project, asks what you want to measure and which analytics accounts you have, then writes the setup, a checklist of what to click in Google Tag Manager, Google Analytics and Meta, and the steps to check it all worked.
Get the prompt, paste it into
your assistant, and answer its questions. That page also lists the six things to check before you trust what it wrote,
and an AGENTS.md block to keep in your repo so later sessions don't re-guess the conventions this one chose.
The prompt is tested: three assistants set up a Next.js app and a Vite app from scratch, and what they got wrong is what those rules are there to prevent.
Working by hand is just as quick for a simple setup: the quick start below, or the integration walkthrough for an app that already has analytics in it.
Adding this to an app that already exists, with analytics already in it? Follow the integration walkthrough instead: same steps, plus how to prove events arrive and how to retire the library you're replacing.
// analytics.ts
import { createAnalytics } from 'react-marketing-tools'
export const analytics = createAnalytics({
// Nothing is stored or sent until you record the visitor's choice; see "Record what the visitor consented to".
consent: 'denied',
gtm: { containerId: 'GTM-XXXXXXX' },
ga4: { measurementId: 'G-XXXXXXX' },
metaPixel: { pixelId: '1234567890123456' },
})Creating the instance does nothing on its own: no scripts load and no cookies are written until your app starts it, which the provider below does. That makes this file safe to import anywhere, including in server-rendered pages.
// main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { AnalyticsProvider } from 'react-marketing-tools'
import { analytics } from './analytics'
import { App } from './App'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<AnalyticsProvider analytics={analytics}>
<App />
</AnalyticsProvider>
</StrictMode>,
)The provider loads the vendor scripts once your app is running in the browser. Anything you track before that is held and sent in order afterwards, so you never lose an event that happened during startup.
// SignUpButton.tsx
import { useAnalytics } from 'react-marketing-tools'
export const SignUpButton = () => {
const { track } = useAnalytics()
return (
<button onClick={() => track('sign_up', { method: 'google' })}>
Sign up
</button>
)
}That one call reaches all three services: Tag Manager gets a sign_up event, Google Analytics 4 gets sign_up, and
the Meta Pixel gets CompleteRegistration, the name Meta uses for the same thing. All three carry the same event ID,
so when the same action arrives twice, from the browser and from your server, Meta counts it once.
Turn it on once, and then mark the elements you care about. No analytics code in your components:
// analytics.ts
import { createAnalytics } from 'react-marketing-tools'
export const analytics = createAnalytics({
consent: 'denied',
gtm: { containerId: 'GTM-XXXXXXX' },
autocapture: { clicks: true },
})// PricingPage.tsx
export const PricingPage = () => (
<section>
<h1>Pricing</h1>
<button
data-analytics-event="cta_click"
data-analytics-param-location="pricing_header"
data-analytics-param-plan="pro"
>
Start free trial
</button>
</section>
)A click anywhere inside that button, including on an icon or text inside it, sends the same event as
track('cta_click', { location: 'pricing_header', plan: 'pro' }).
data-analytics-eventis the event name. Everydata-analytics-param-*attribute becomes one detail on the event, with dashes turned into underscores:data-analytics-param-button-textarrives asbutton_text.- Values are text. For numbers, such as a price you want to add up, call
track()instead. - It works for elements added later, for example after a route change, and for links as well as buttons.
- If several marked elements are nested, the closest one to the click wins.
A journey groups the steps of a flow such as a checkout, so you can see where people drop out:
// Checkout.tsx
import { useState } from 'react'
import { useAnalytics } from 'react-marketing-tools'
export const Checkout = () => {
const { journey } = useAnalytics()
// Create it once for this flow, not on every render: each journey has its own ID.
const [checkout] = useState(() => journey('checkout'))
return (
<>
<button onClick={() => checkout.step('shipping')}>Continue to payment</button>
<button onClick={() => checkout.complete({ value: 42, currency: 'USD' })}>Pay</button>
<button onClick={() => checkout.abandon('changed_mind')}>Cancel</button>
</>
)
}The first call also sends a journey_start event, and every event in the flow carries the same journey ID. In Google
Analytics 4 you can then build a funnel from journey_start, journey_step and journey_complete.
// after your sign-in code succeeds
import { analytics } from './analytics'
analytics.identify('user-42', { email: 'ada@example.com' })The ID is your own user ID: it goes to Tag Manager and Google Analytics 4 so you can join sessions to accounts. The
email address is not sent to Google. It goes only to Meta, which uses contact details to match a visitor to a Facebook
or Instagram account, and it's scrambled into an unreadable fingerprint (a hash) before it's sent. Call
analytics.reset() when someone signs out.
// from your cookie banner, when the visitor answers
import { analytics } from './analytics'
analytics.consent.update({ analytics: 'granted', ads: 'granted' })Until this is called, the instance follows the consent value you passed when you created it. The library passes the
choice to Google's Consent Mode, which is how Google's tags are told what a visitor agreed to, and to the Meta Pixel,
which holds events until it's allowed to send them. The same choice controls what the library itself stores in the
browser. Both parts of the choice can be set separately if your banner asks separately.
import { analytics } from './analytics'
// inside an async function, any time after your app has started
const visitorId = await analytics.getVisitorId()This is a random ID kept in the browser, given only to visitors who consented to analytics, so you can join a person's visits in your own systems. It's never sent to Google Analytics. You can swap it for a browser fingerprint if you want to recognise people who clear their cookies; see Visitor ID.
Core Web Vitals are Google's three measures of page experience: how fast the main content appears, how quickly the page responds to a tap or click, and how much the layout jumps around. Install Google's measuring library and pass it your instance:
npm install web-vitals@^6// main.tsx, after the render call
import { trackWebVitals } from 'react-marketing-tools/web-vitals'
import { analytics } from './analytics'
void trackWebVitals(analytics)Each measurement arrives as an event named LCP, INP or CLS in Google Analytics 4 and Tag Manager. They're never
sent to Meta.
Some things happen where the browser can't see them, such as a payment your payment provider confirms minutes later. Other events simply never make it, because an extension or browser setting blocks the tracking scripts. For both, send the event from your server.
// inside your payment webhook, where `order` is the order you just confirmed
import { sendMeasurementProtocolEvent } from 'react-marketing-tools/server'
await sendMeasurementProtocolEvent({
measurementId: 'G-XXXXXXX',
apiSecret: process.env.GA4_API_SECRET!,
// Saved when the visitor started checkout, with readGa4Cookies() from the same import.
clientId: order.ga4ClientId,
events: [
{
name: 'purchase',
params: { transaction_id: order.id, value: order.total, currency: 'USD' },
},
],
})Events from an iOS or Android app go to its GA4 app stream the same way, with firebaseAppId and appInstanceId in
place of measurementId and clientId; see
Events from a mobile app.
The same import has sendConversionsApiEvent for Meta, which scrambles customer details into hashes the way Meta
requires before sending them.
Point the page at an address on your own site, and mount the handler there. Every event the Pixel receives is then sent from your server as well, and Meta counts each one once:
// analytics.ts
import { createAnalytics } from 'react-marketing-tools'
export const analytics = createAnalytics({
consent: 'denied',
metaPixel: { pixelId: '1234567890123456' },
server: { endpoint: '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/api/track' },
})// app/api/track/route.ts — a Next.js route; any server that speaks Request and Response works
import { createTrackHandler } from 'react-marketing-tools/server'
export const POST = createTrackHandler({
allowedOrigins: ['https://shop.example.com'],
meta: {
pixelId: '1234567890123456',
accessToken: process.env.META_CAPI_TOKEN!,
},
})// analytics.ts
import { createAnalytics } from 'react-marketing-tools/core'
export const analytics = createAnalytics({
consent: 'granted',
gtm: { containerId: 'GTM-XXXXXXX' },
})
analytics.start() // the React provider does this for you
analytics.track('sign_up', { method: 'google' })| Google Tag Manager | Google Analytics 4 | Meta Pixel | Your server, for Meta | |
|---|---|---|---|---|
track() |
the event in the dataLayer, with an event ID | the event through Google's tag | Meta's name for the event, with the same event ID | the same event again, so it still arrives when the browser is blocked |
identify() |
your user ID | your user ID | contact details, hashed by the Pixel | contact details, hashed by your server |
consent.update() |
Google Consent Mode | Google Consent Mode | permission to send, or to hold | only sends with permission to share data with ad platforms |
| Campaign the visitor came from | the campaign on every event | read from the page address by Google's tag | the Meta click ID | the Meta click and browser IDs |
- All docs
- Getting started
- Integration walkthrough: add it to an existing app, step by step, and prove events arrive
- React: provider, hook, single-page apps
- Next.js: a complete App Router setup, client and server
- Tracking events: naming rules, page views, journeys, click autocapture, Web Vitals, users, personal data, errors
- Configuration
- Consent: Consent Mode v2 and Global Privacy Control
- Attribution: UTM params and ad click IDs
- Visitor ID: a stable ID for consenting visitors, random or fingerprint
- Google Tag Manager
- Google Analytics 4
- Meta Pixel
- Other tools: PostHog, Umami, Plausible, your own endpoint
- Measuring AI activity: AI features, visits from AI assistants, AI crawlers
- Server-side tagging
- GA4 Measurement Protocol: GA4 events from your server
- Meta Conversions API: Meta events from your server
- Error tracking: report errors to Google Analytics 4
- Debugging: see what's sent, and fix common problems
- Migrating from 0.4
- API summary: every option, method and limit on one page
- Set up with an AI assistant: a prompt that interviews you and writes the setup
- Changelog