@pro-laico/tracking
Turn analytics on and off from the Payload admin: flip a switch for PostHog, Google Tag Manager, or Vercel Analytics and the right scripts load on your site.
@pro-laico/tracking lets you manage analytics from the Payload admin instead of your environment. Toggle PostHog, Google Tag Manager, or Vercel Analytics on, paste in the keys right in the dashboard, and a provider you add once at your app root loads exactly the tools you've turned on.
Installation
pnpm add @pro-laico/trackingnpm install @pro-laico/trackingyarn add @pro-laico/trackingpayload, next, and react are peers you already have in a Payload + Next.js app. The analytics SDKs are optional peers, so install only the ones you turn on: posthog-js for PostHog, @next/third-parties for Google Tag Manager, @vercel/analytics for Vercel Analytics.
Setup
@pro-laico/core comes bundled with this plugin — it's a dependency, not a separate install. Its cached reads reach Payload through a config you register once in your Next.js instrumentation hook (registerPayloadConfig); see @pro-laico/core → Setup to wire it up.
Adding analytics to your own Payload + Next.js project.
Add the plugin to your Payload config
import { buildConfig } from 'payload'
import { trackingPlugin } from '@pro-laico/tracking'
export default buildConfig({
plugins: [trackingPlugin()],
})This registers the Tracking global, where you turn each analytics tool on and enter its keys.
Turn on the tools you want in the admin
Open the Tracking global (under the Tracking admin group). Each tool has its own toggle in the sidebar; flipping one on reveals its settings:
- PostHog: enter the project public key and host (defaults to
https://us.i.posthog.com). Autocapture is on by default; you can narrow it with optional URL allow / ignore lists. - Google Tag Manager: enter your container ID (
GTM-XXXXXXX). - Vercel Analytics: just the toggle; it has no keys.
Each tool's keys are required only when its toggle is on, so you can save the global with anything you're not using turned off.
Add the provider to your app root
The keys live in the Tracking global, so nothing loads until you fetch that global on the server and hand it to TrackingProvider. Read it with the cached getter (getCachedTracking) so the page reads it once per request, and wrap your frontend layout with the provider. It reads the toggles and renders only the providers you enabled (and renders nothing at all if tracking is missing):
// app/(frontend)/layout.tsx (a Server Component)
import { draftMode } from 'next/headers'
import { getCachedTracking } from '@pro-laico/tracking/cache'
import { TrackingProvider } from '@pro-laico/tracking/provider'
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const { isEnabled: draft } = await draftMode()
const tracking = await getCachedTracking(draft)
return (
<html lang="en">
<body>
<TrackingProvider tracking={tracking}>{children}</TrackingProvider>
</body>
</html>
)
}TrackingProvider is a client component that pulls in the optional SDKs, which is why it lives behind the /provider subpath: server tooling never has to load posthog-js or the others. You fetch the global on the server and thread it down as a prop.
Register your Payload config
getCachedTracking reaches Payload's Local API through the config you register once in your instrumentation hook (see the callout above and @pro-laico/core → Setup). With that in place, saving the Tracking global revalidates the tracking tag, so a change in the admin flows through to the next page view.
The atomic-payload template already includes the plugin and wires TrackingProvider into the frontend layout (reading the Tracking global through its cached getter). You just choose what to enable.
Turn on the tools you want
Open the Tracking global in the admin. Flip on PostHog, Google Tag Manager, or Vercel Analytics, and fill in the keys each one asks for: the PostHog public key and host, or your Google Tag Manager ID.
Save and reload
The layout reads the global on each request, so once you save your changes the enabled tools load on the next page view. No build step or environment variables to set.
PostHog initialises once on the first non-admin page view and relies on autocapture for events: it records pageviews and interactions automatically. (The earlier custom-property collection and field were removed; the only PostHog tuning is the optional URL allow / ignore lists for autocapture.)
Caching & revalidation
The read this package ships, getCachedTracking(draft) from @pro-laico/tracking/cache, returns the Tracking global so your layout reads the toggles and keys once instead of re-querying Payload on every render. Saving the global in the admin revalidates the tracking tag, so the next page view loads exactly the tools you just changed.
This is the getter the template's layout uses to feed TrackingProvider (the standalone Setup above shows the same wiring). See Caching & revalidation for how the tags and withCache work.
Options
trackingPlugin(options?) accepts:
Prop
Type
Both options are flat booleans, so there are no nested keys to expand. All options at their defaults, as a working starting point:
trackingPlugin({
enabled: true,
includeTrackingGlobal: true,
})Environment variables
This plugin reads no environment variables. Every key (the PostHog public key and host, the Google Tag Manager ID) is entered and stored in the Tracking global in the admin, so the same build works across deployments without juggling per-environment keys.
Exports
Everything @pro-laico/tracking exposes, grouped by where you reach for it.
The plugin
Export
Type
trackingPluginplugin
Parameters
options?:TrackingPluginOptionsSee the Options table above. Both keys are optional and default to true.Returns
PluginA Payload config plugin you add to buildConfig({ plugins: [...] }).Example
import { buildConfig } from 'payload'import { trackingPlugin } from '@pro-laico/tracking'export default buildConfig({plugins: [trackingPlugin()],})Location
@pro-laico/trackingTrackingPluginOptionstype
Location
@pro-laico/trackingFrontend
Export
Type
TrackingProviderfunction
children. Renders just children when tracking is missing. Client component.Parameters
tracking:Tracking | undefinedThe Tracking global, fetched on the server (with getCachedTracking). Its toggles decide which providers render.children:ReactNodeYour app tree, wrapped by every enabled provider.Returns
JSX.ElementThe children wrapped in the enabled analytics providers.Example
import { getCachedTracking } from '@pro-laico/tracking/cache'import { TrackingProvider } from '@pro-laico/tracking/provider'// app/(frontend)/layout.tsxexport default async function RootLayout({ children }) {const tracking = await getCachedTracking(false)return ( <html lang="en"> <body> <TrackingProvider tracking={tracking}>{children}</TrackingProvider> </body> </html>)}Location
@pro-laico/tracking/providerPostHogProviderfunction
Parameters
tracking:Tracking | undefinedSupplies postHogPublicKey, postHogHost, and the optional autocapture allow / ignore lists.children:ReactNodeYour app tree, rendered inside PostHog provider.Returns
JSX.ElementThe children wrapped in PostHog provider.Example
import { getCachedTracking } from '@pro-laico/tracking/cache'import { PostHogProvider } from '@pro-laico/tracking/provider'// only the PostHog provider, wired by hand in a layoutconst tracking = await getCachedTracking(false)return <PostHogProvider tracking={tracking}>{children}</PostHogProvider>Location
@pro-laico/tracking/providerGoogleTagManagerProviderfunction
tracking.googleTagManagerId is set.Parameters
tracking:Tracking | undefinedSupplies googleTagManagerId (the GTM-XXXXXXX container ID).children:ReactNodeYour app tree, rendered alongside the GTM script.Returns
JSX.ElementThe children plus the GTM script.Example
import { getCachedTracking } from '@pro-laico/tracking/cache'import { GoogleTagManagerProvider } from '@pro-laico/tracking/provider'const tracking = await getCachedTracking(false)return <GoogleTagManagerProvider tracking={tracking}>{children}</GoogleTagManagerProvider>Location
@pro-laico/tracking/providerVercelProviderfunction
<Analytics /> component alongside your tree; it needs no keys.Parameters
children:ReactNodeYour app tree, rendered with the Vercel <Analytics /> component.Returns
JSX.ElementThe children plus the Vercel <Analytics /> component.Example
import { VercelProvider } from '@pro-laico/tracking/provider'// Vercel Analytics takes no keys, so it needs no tracking propreturn <VercelProvider>{children}</VercelProvider>Location
@pro-laico/tracking/providerCache getters
Export
Type
getCachedTrackingfunction
withCache under the tracking tag and revalidated when you save the global, so the layout reads it once per request instead of re-querying Payload on every render. This is what you pass to TrackingProvider.Parameters
draft:booleanRead the draft (true) or published (false) variant of the global.Returns
Promise<Tracking | undefined>The Tracking global, or undefined if it has never been saved.Example
import { draftMode } from 'next/headers'import { getCachedTracking } from '@pro-laico/tracking/cache'import { TrackingProvider } from '@pro-laico/tracking/provider'// app/(frontend)/layout.tsxexport default async function RootLayout({ children }) {const { isEnabled: draft } = await draftMode()const tracking = await getCachedTracking(draft)return <TrackingProvider tracking={tracking}>{children}</TrackingProvider>}Location
@pro-laico/tracking/cacheTypes
Export
Type
Tracking (type)type
Location
@pro-laico/tracking/schemaRelated
@pro-laico/fonts
Manage custom fonts in the Payload admin and use them with next/font/local: upload your fonts, pick the active ones, and a build step delivers them to your app.
@pro-laico/atomic
Turns the nested blocks editors build in the Payload admin into a working, interactive website: reusable content blocks, point-and-click actions, and a complete form pipeline.