chmonitor
OperateAdvanced

Product Analytics

Opt-in PostHog product analytics across every chmonitor surface — off by default, Do Not Track respected, cookieless, no session recording.

chmonitor can optionally send product analytics — automatic pageviews, autocaptured interactions (clicks, form submits), client- and server-side crashes, plus explicit conversion funnel events (signups, cluster connects, upgrades, checkouts) — to PostHog for the hosted SaaS product's growth/business side. It is OFF by default — a hard no-op — until you configure an analytics key.

Coverage spans every surface of chmonitor.dev: the dashboard (dash.chmonitor.dev, incl. server/API crash capture), the docs site (docs.chmonitor.dev), the landing site, and the blog.

This is a different system from Product Telemetry (anonymous, on-by-default, aggregate OSS instance telemetry to chmonitor's own collector) and from Self-Tracking (writes usage events to your own ClickHouse instance). Product analytics exists purely to measure the marketing/signup/upgrade funnel and is never required to run chmonitor.

Opt in — off by default

No key, no network call

Without CHM_ANALYTICS_KEY (dashboard) / VITE_ANALYTICS_KEY (docs) / PUBLIC_ANALYTICS_KEY (landing + blog) set, none of this code makes a network call, loads PostHog, or sets a cookie. Self-hosted instances stay completely untouched by default.

VariableSurfacePurpose
CHM_ANALYTICS_KEYDashboard (derives client VITE_ANALYTICS_KEY; also read server-side for crash capture)PostHog project key. Empty = disabled.
CHM_ANALYTICS_HOSTDashboard (derives client VITE_ANALYTICS_HOST)PostHog ingestion host. Empty = PostHog Cloud (US).
VITE_ANALYTICS_KEYDocs site (TanStack Start)PostHog project key. Empty = disabled.
VITE_ANALYTICS_HOSTDocs site (TanStack Start)PostHog ingestion host. Empty = PostHog Cloud (US).
PUBLIC_ANALYTICS_KEYLanding + blog (Astro)PostHog project key. Empty = disabled.
PUBLIC_ANALYTICS_HOSTLanding + blog (Astro)PostHog ingestion host. Empty = PostHog Cloud (US).

Both *_HOST variables let you point at a self-hosted PostHog instance instead of PostHog Cloud, keeping the whole stack self-hostable in spirit.

Do Not Track

The browser navigator.doNotTrack signal is checked before analytics initializes — when set, init is skipped entirely. The dashboard additionally honors the cross-tool DO_NOT_TRACK/ VITE_DO_NOT_TRACK convention already used by telemetry.

Events

Landing site

EventWhen it fires
landing_viewThe marketing home page (/) loads
pricing_viewThe /pricing page loads
cta_clickA tracked call-to-action link is clicked (target = a static identifier, e.g. hero-primary)

Dashboard

EventWhen it fires
signupA Clerk account created within the last 2 minutes loads the app (approximates "just signed up")
cluster_connectA ClickHouse connection is added and saved
sample_cluster_connectedThe "Try with sample ClickHouse" read-only preset is connected
sample_to_real_convertedA non-sample host is added after the sample was connected
first_chart_renderThe first chart renders with real data (once per page load)
agent_messageA message is sent to the AI agent
advisor_recommendation_viewedThe query advisor shows at least one recommendation (via /advisor or the agent chat)
paywall_hitA request comes back with a classified billing-limit 402 and the paywall modal opens (reason, plan_id)
upgrade_clickAn "Upgrade" call-to-action is clicked
checkout_startedA Polar checkout session is created, right before redirect
checkout_completed(server-side) The hosted Polar checkout reaches succeeded (checkout.updated webhook) — payment confirmed, subscription not yet live
upgrade_completed(server-side) A new Polar subscription goes live (subscription.created, active/trialing) — confirms money actually moved
payment_succeeded(server-side) An order is paid (order.paid webhook) — first purchase or renewal (amount_cents, currency, billing_reason)
payment_refunded(server-side) An order is refunded (order.refunded webhook) (amount_cents, currency)
subscription_canceled(server-side) A subscription is canceled (subscription.canceled webhook)

The core acquisition→conversion funnel these events trace: landing_viewsignupcluster_connectadvisor_recommendation_viewedpaywall_hitupgrade_completed. Build this as a PostHog funnel insight (Product analytics → Insights → Funnel) using these events in order.

upgrade_completed fires from the Polar webhook (server-side, no browser context), so it carries the browser's PostHog distinct id rather than the shared server one: checkout_started reads posthog.get_distinct_id() and forwards it as posthogDistinctId on the Polar checkout metadata, Polar propagates it onto the resulting subscription, and the subscription.created webhook reads it back off to stitch upgrade_completed onto that same distinct id (#2478). Older checkouts or a manually-created Polar subscription have no such metadata; the webhook falls back to the shared server id for those, so it stays a per-user funnel step for stitched conversions and an aggregate count otherwise.

What is captured

  • Automatic pageviews and pageleaves on every surface (SPA route changes included)
  • Autocaptured interactions — clicks and form submissions (PostHog autocapture)
  • Crashes — client-side React error boundaries report a $exception; the dashboard Worker additionally reports server/API exceptions via PostHog's stateless /capture/ endpoint (the Worker runs on workerd, so it cannot use posthog-node)
  • Explicit funnel events — the typed events listed above

What is still NOT collected

  • Session recordingsdisable_session_recording: true on every surface
  • No identify() is ever called with a user-derived id — every distinct id stays PostHog's anonymous default
  • Explicit dashboard events are redacted defensively before leaving the process, reusing the same redactProps allowlist Product Telemetry uses (drops any prop whose key implies sensitive content, or whose value looks like an email/IP/URL); server-side crash props are redacted the same way

Autocapture can record clicked-element text

Because autocapture is enabled, PostHog may record the text of clicked elements. The two safety rails that keep self-hosted instances silent are unchanged: it is off unless a key is set, and the browser Do Not Track signal disables it entirely.

PostHog is configured cookieless (persistence: 'localStorage'), so no cookie banner is required for this feature.

Anonymous by design

No call site ever calls PostHog's identify() with anything user-derived — every distinct id stays PostHog's anonymous default.

On this page