DocsFeatures Adoption

Features Adoption — 2-min intro

How we track what our users actually do in the product. The quick-start block below explains the three thresholds every reader needs; the collapsible sections cover the full pipeline (ingestion, aliases, URL parsing, KPIs) when you need to debug or extend.

Getting started

The 3 thresholds that matter

Everything else in this page is just plumbing around these three numbers.

Touched
≥ 60s

Minimum active time for a feature to count as "used" in a day. Below 60s = ignored (noise filter).

Engaged ≥1h (legacy, uncalibrated)
≥ 2 feats or ≥ 3600s

Legacy threshold behind the Activation Rate KPI and the Product Adoption "≥1h share" column. Not validated against churn — correlation analysis shows the real predictors are core-CRM activation (≥60s binary) and the 30d health slope. Slated for calibration by the Health Lab.

Adoption window
30 days

Rolling window used by Depth / adoption tables / health scoring.

Core CRM ≠ any feature. Contacts, Conversations and Opportunities are the 3 features that predict conversion (+40 pts lift on conversion, per correlation analysis). The Health Score v3 Activation pillar uses a ≥60s binary on core CRM — this is the empirically validated signal, not the 1h threshold on the left.

Deep dives

Open a section to see ingestion plumbing, alias tables, URL parser rules, and KPI definitions.

Pipeline

Ingestion & feature normalization

How a browser event becomes a row in product_adoption_daily.

1) Browser tracking

The deployed GoHighLevel agency custom JS sends usage events to /api/usage/batch with location, feature_key, day, sec and url.

2) Batch ingestion

POST /api/usage/batch calls Supabase RPC gocroco_usage_ingest_batch and stores usage into feature_daily, feature_monthly, user_feature_lifetime and events. Custom links are reparsed from URL so feature_key becomes the custom feature id.

3) Feature normalization

normalize_feature_key(raw) maps aliases from feature_catalog to canonical keys used for adoption metrics.

4) Product Adoption dashboard

product_adoption_daily drives charts/tables. Unknown keys are grouped into Other and exposed in the Other breakdown card.

Batch payload contract

Production endpoint: https://gocroco.vercel.app/api/usage/batch

FieldRequiredNotes
emailYesUser email in GHL context.
location_idYesPrimary account scope for aggregation.
feature_keyYesRaw feature key before SQL normalization.
dayOptionalISO date bucket, defaults to Europe/Paris current day.
secYesTracked active duration in seconds.
urlOptionalUsed for events/user_last_seen diagnostics and geo context.
Catalog

Features currently tracked

23 canonical keys from lib/features.ts — imported live.

#LabelFeature keyNotes
1DashboarddashboardTracked in current feature list.
2ConversationsconversationsTracked in current feature list.
3CalendarscalendarsTracked in current feature list.
4ContactscontactsTracked in current feature list.
5OpportunitiesopportunitiesTracked in current feature list.
6PaymentspaymentsTracked in current feature list.
7MarketingmarketingTracked in current feature list.
8EmailemailCanonical key. Raw `emails` is aliased to `email`.
9AutomationautomationTracked in current feature list.
10ReputationreputationTracked in current feature list.
11BlogsblogsTracked in current feature list.
12MembershipsmembershipsTracked in current feature list.
112 of 23 features
Aliases

Normalization mapping (raw → canonical)

URL parser aliases + aggregation aliases (lib/featureAggregation.ts) + Supabase feature catalog aliases.

RawCanonicalSource
emailsemailAggregation alias (lib/featureAggregation.ts)
qr-codesmarketingURL parser + aggregation alias + DB alias
qr-codemarketingAggregation alias (lib/featureAggregation.ts)
page-builderfunnels-websitesURL parser + aggregation alias + DB alias
quiz-buildersurvey-builderURL parser + aggregation alias + DB alias
quiz-builder-v2survey-builderURL parser + aggregation alias + DB alias
survey-builder-v2survey-builderAggregation alias (lib/featureAggregation.ts)
form-builder-v2form-builderURL parser + aggregation alias + DB alias
workflowautomationAggregation alias + DB alias
analyticsdashboardURL parser + aggregation alias + DB alias
Also exposed as groups: forms-survey-quiz-builder bundles form-builder + survey-builder in UI filters.
URL parsing

parseFeatureFromUrl() rules

How URLs are mapped to a feature_key. Same indexOf('location') logic matches /v2/ and legacy paths.

URL patternfeature_keyNotes
/v2/location/{id}/marketing/emails/...emailSpecial rule in parseFeatureFromUrl().
/location/{id}/emails/...emailSame indexOf('location') logic, matches /v2/ and legacy paths.
/v2/location/{id}/custom-menu-link/{custom_feature_id}{custom_feature_id}feature_raw keeps custom-menu-link.
/v2/location/{id}/custom-page-link/{custom_feature_id}{custom_feature_id}feature_raw keeps custom-page-link.
Unknown feature segmentotherFallback in parseFeatureFromUrl().
KPIs

Activation Rate, Funnel, Retention, CSM Signals

The metrics powering the /product-adoption dashboard.

KPIDefinitionWhere to see itSource
Activation Rate (legacy)New users reaching ≥ 2 features OR ≥ 3600s of total usage within 7 days of first event. The 1h threshold is uncalibrated — prefer the Health Score v3 Activation pillar for churn-predictive signal./product-adoption KPI gridlib/analytics/product/queries/kpis.ts
Activation Funnel5 steps: first event → 2+ features → 5+ features → ≥1h total → active on day 7. The ≥1h step is a legacy threshold, not empirically validated./product-adoption Funnel panellib/analytics/product/queries/funnel.ts
Retention HeatmapWeekly cohorts — % of users from week N still active in week N+k./product-adoption Retention heatmapcomponents/... RetentionHeatmap
Feature DepthDistinct features touched ≥ 60s in the last 30 days (capped at 5, Settings + AI Agents excluded).Health Score (Depth pillar) + Product Adoption feature breakdownscripts/migrations/086_unify_health_to_v3.sql
CSM SignalsAt-risk locations (score dropping), drifting users, declining features week-over-week./product-adoption CSM Signals panelCsmSignalsPanel component
Unknown keys

What goes into 'Other'

Null and unknown feature keys end up here. Investigate new entries to decide if they deserve canonicalization.

Anything not recognized after aggregation is grouped into Other.

custom-menu-linkcustom-page-linkblog-builderlaunchpadtasksreporting
-- Inspect Other breakdown by hours (last 30 days)
SELECT feature_key, round(sum(time_sec) / 3600.0, 2) AS hours
FROM public.product_adoption_daily
WHERE event_date >= current_date - interval '30 days'
  AND feature_key NOT IN (
    'dashboard','conversations','calendars','contacts','opportunities',
    'payments','marketing','email','emails','automation','reputation',
    'blogs','memberships','integration','settings','funnels-websites',
    'form-builder','survey-builder',
    '66a97ca06e16787562476424','5e9efed3-e795-4fe6-8e56-9f9bb6c730fb',
    '0bae2e9d-79d4-4513-84e1-a3056dc45acb','other'
  )
GROUP BY 1
ORDER BY hours DESC;
Gotchas

Caveats & known drift

Things to keep in mind when debugging unexpected numbers.

Settings and AI Agents are excluded from the used_any_feature flag used by the Health Score Activation pillar — this exclusion happens in SQL (migration 086), not in the TS lib. A user who only opens Settings will show as "no feature activated" in the health score.

The deployed GHL custom JS is managed outside git. Documentation and ingest contract can drift if sync discipline is not enforced.

Historical rows may still contain custom-menu-link / custom-page-link from before the API normalization fix. New ingests rewrite these to custom feature IDs.

The Product Adoption page shows an Other breakdown panel to identify which keys are driving the catch-all bucket.

Related features