Skip to main content
This page covers Amplitude as a server-side analytics adapter: dual identity on track, identify via the Node SDK’s identify pattern, and alignment with session-backed anonymousAnalyticsId and login flows.

Why

Amplitude gives you a production-grade analytics pipeline without any infrastructure to manage. You instrument events on the backend, and Amplitude handles ingestion, storage, and visualisation. It’s the right choice when you need funnel analysis, retention metrics, or cohort-based product insights.

How it works

Dual identity on track: Every track call receives { anonymousId: string; userId?: string }. The adapter maps these to Amplitude’s device_id (anonymous id) and optional user_id when authenticated, consistent with Node SDK usage. identify: identify({ anonymousId, userId }) links device and user in Amplitude via the SDK’s identify API (see implementation in apps/tools/analytics/amplitude/src/index.ts). When identify runs (server-only): Same as other providers — after successful login via identifyUserInAnalytics, and via syncAnalyticsIdentityIfNeeded when a new anonymous id is created for an already-authenticated session (legacy repair). No browser Amplitude SDK is wired in this stack yet; instrumentation is backend-only through tools.analytics and GraphQL trackFrontendEvent.

Setup

  1. In apps/backend/src/configuration/production.ts (and staging.ts if you have one), set tools.analytics:
  2. In your production environment, set:
    ANALYTICS_APP is required whenever analytics is enabled. The backend attaches it as the app property on every outbound track event.
  3. Keep development.ts and test.ts using the local client — Amplitude should never receive dev or test traffic.
  4. Run make test module=backend path=apps/backend/src/tools/analytics/__tests__/loader.spec.ts.

Tracking events

Call tools.analytics.track() with an event name, a properties object, and identification attributes:
Keep event names in snake_case. Keep property keys bounded and low-cardinality where used for filtering.

Frontend tracking endpoint

Frontend analytics events should flow through trackFrontendEvent on GraphQL. This avoids direct provider SDK usage in the browser and keeps rate limiting plus payload constraints on the backend.
Rate limiting for anonymous traffic prefers anonymous:session:<anonymousAnalyticsId> when the session id is available (apps/backend/src/gql/middlewares/rateLimitFrontendTracking.ts).

Future: browser SDK and one canonical id

When you add a browser Amplitude SDK, the intended pattern is to surface the same anonymousAnalyticsId the backend stores in session — for example a first-party cookie on responses — so the client uses the same device id, not a second generator. HttpOnly vs JS-readable and SameSite are decisions for that phase.

Extending this system

  1. Change track/identify mapping in apps/tools/analytics/amplitude/src/index.ts.
  2. If you change the shared contract, update apps/shared/src/analytics/types.ts and other adapters.
  3. Run make test module=tooling-analytics-amplitude and make test module=backend path=apps/backend/src/tools/analytics/__tests__/loader.spec.ts.

What not to do

❌ Never import Amplitude directly in a GraphQL resolver or React component. Use tools.analytics from the backend. ❌ Never send Amplitude traffic from development.ts or test.ts — it pollutes your production data and your event quota. ❌ Never use unbounded strings as metric tags; follow .cursor/rules/security.mdc.

File conventions

Gotchas

  • amplitude.init() is called once at startup inside apps/tools/analytics/amplitude/src/index.ts. Do not call it elsewhere.
  • Use trackingPolicy.allowedFields for every allowed property key. Unknown keys are dropped before emission.
  • The node SDK batches and flushes events asynchronously. A process crash before the flush window may drop the last few events — this is expected behaviour for fire-and-forget analytics.
  • Analytics providers expose shutdown(), and backend startup wires graceful shutdown hooks to reduce event loss during process termination.

What’s next?

  • Local — the dev stub to use in development.ts and test.ts.
  • PostHog — alternate production analytics provider.
  • Configuration — switching pluggable tool implementations.