Countly integration
Agent Skill for AI coding agents: integrate Countly analytics with a proper data collection model (Claude Code, Cursor, Codex, Copilot, any LLM)
npx -y skills add Countly/countly-sdk-skill --skill countly-integrationAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 27 days oldThe repository was created 27 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Integrate Countly product analytics into any app (web, iOS, Android, Flutter, React Native, or server-side via HTTP) with a data collection model designed so the Countly dashboard can actually answer product questions. Use this skill whenever the user wants to add Countly, add analytics/tracking to an app, instrument events, track sessions/views/users/crashes, design an event taxonomy or tracking plan, wire up login/logout identity for analytics, or asks why their Countly dashboard can't answer a question (funnels, retention, cohorts, drill). Trigger even if they only say "add analytics" and Countly is the platform in use, or mention an app_key / count.ly / countly server URL.
SKILL.md
14.7 KB, ~3.3k tokens by cl100k_base, as published. Nobody here has run it
Countly Integration with a Proper Data Collection Model
Countly dashboards (Drill, Funnels, Retention, Cohorts, Flows, User Profiles) are only as good as the data model behind them. Analytics data is retroactive-proof but not retro-fillable: funnels and cohorts can be defined later at no cost, but data you didn't collect is gone forever — and data collected with wrong granularity (high-cardinality event keys, IDs in segmentation values) actively poisons the dashboard. So the integration order is always: design the data model first, write SDK calls second.
Workflow
- Collect the questions. Ask the user (or infer from the product) which questions the dashboard must answer. Write them down explicitly — e.g. "Where do users drop off during onboarding?", "Do paying users retain better?", "Which plan tier crashes most?"
- Map each question to a Countly feature and its required data (table below). This yields the tracking plan: which events, which segmentation keys, which user properties, whether sessions/views/crashes are needed.
- Choose the identity strategy (section below) — this is the hardest thing to change later.
- Write the tracking plan down (a
TRACKING.mdor similar in the user's repo): event keys, segmentation keys with example values, user properties, who sets them and when. Get user sign-off on this before writing code if the plan is non-obvious. - Implement: init → consent (if required) → identity → sessions → events/views/user properties → crashes. Per-SDK method names are in references/sdk-methods.md; raw HTTP API for server-side integrations is in references/http-api.md.
- Verify against the checklist at the end.
What powers what — question → data mapping
| Dashboard question | Feature | Data it needs |
|---|---|---|
| How many/active users, new vs returning, session length, top countries/devices/versions | Analytics overview | Sessions (begin/update/end) + metrics sent with begin_session |
| Where do users drop off in flow X? | Funnels | Events — one event per step (or one event + step segmentation), consistent per-user identity |
| Do users come back? (Day-N retention) | Retention | Sessions (session retention) or a specific event (event retention) |
| Which users did X but not Y? Target/segment them | Cohorts | Events + custom user properties |
| Ad-hoc: "signups from Germany on Android with plan=pro" | Drill | Events with rich segmentation + user property snapshot (captured automatically at event time) |
| What paths do users take? | Flows | Events or views in sequence |
| Which screens/pages are used, time-on-screen, bounces | Views analytics | Views with normalized names |
| Is the app stable? Which version/device crashes? | Crashes | Automatic crash handler + handled exceptions + breadcrumbs |
| Who is this user? (support lookups, tier breakdowns) | User Profiles | user_details predefined + custom properties |
| How do users rate us / NPS? | Feedback | Feedback widgets (server-configured, one SDK call) |
If a question maps to no collected data, the dashboard cannot answer it — add the event/property now, not when someone asks.
Identity strategy (decide first)
Countly identifies users by device_id; internally the user is
sha1(app_key + device_id), so identity is per-app and merging is a real
server-side operation. Four documented strategies — pick one deliberately:
- Device-based (default) — SDK-generated ID, no login concept. Fine for anonymous-only products.
- Known users only — init Countly only after login, passing the stable user ID as device ID. No anonymous data at all.
- No anonymous data, but init early — start SDK in temporary/offline ID
mode; on login call
setID(userId)(queued data gets re-stamped and flushed); on logout re-enter temporary mode. - Anonymous → merge on login (most common for consumer apps) — start
with SDK-generated ID; on first login call
setID(userId). Because the previous ID was SDK-generated, the server merges the anonymous history into the known user. On logout, do nothing — leave the device ID as is. On a later login, compare: if the same user, do nothing; if a different account, callsetID(newUserId)— the current ID is developer-supplied, so it switches without merging, which is exactly right for an account change.
Rules that prevent painful surprises:
setIDmerges only when the current ID is SDK-generated. If the current ID is developer-supplied, it switches identity without merge — a new user is created, the session ends, timed events are dropped, and consent is reset.- Merging is a costly server-side operation — design for at most one merge per user (the first login). Never reset to a fresh anonymous ID on logout: every logout→login cycle would mint a new anonymous user and force another merge.
- Use the same user ID across platforms if cross-device identity matters.
- Prefer an opaque customer/user ID over email as device_id — the identifier
rides in every request and log, so email there is PII exposure. If the
organization's privacy policy permits it, email is acceptable (it makes
finding users in the dashboard easier); either way normalize
developer-supplied IDs before every
setID(lowercase, trim) —[email protected]and[email protected]are two different users.
Sessions
Sessions anchor retention, loyalty, session frequency/duration, and carry the device metrics (OS, version, device, resolution, carrier, locale, app version). Always enable session tracking unless the integration is a stateless backend service.
- Mobile SDKs track sessions automatically (foreground/background). Web
requires opting in:
Countly.track_sessions(). - The wire model is
begin_session=1(+ metrics) →session_duration=Nheartbeat every ~60 s →end_session=1. The server merges sessions that restart within the cooldown window (15 s default). - Metrics are only accepted with
begin_session— a mid-session app-version change shows up on the next session. - Server caps a single reported duration (
session_duration_limit, default 86400 s). - Use manual session control only for non-standard lifecycles (kiosk apps, media players, server-side); otherwise automatic is correct.
Events — the heart of the data model
An event is {key, count, sum, dur, segmentation}. Design rules, each of
which exists because of a hard server limit or a dashboard behavior:
Few keys, rich segmentation. The server allows 500 unique event keys per
app, but a good model uses 20–80. One Purchase event segmented by
{category, paymentMethod, plan} beats PurchaseBook/PurchaseSub/… —
funnels, drill and flows all work by filtering segmentation, and every extra
key clutters every event dropdown in the dashboard.
Segmentation values must be low-cardinality. Max 1,000 distinct values
per segmentation key before aggregated views degrade. Good values: enums,
booleans, bucketed numbers ("1-10", "11-50"), normalized names. Never
put user IDs, emails, timestamps, raw URLs, session IDs, or free text in
segmentation — that data belongs in user properties, or nowhere.
Name for humans. Keys like signup-completed, Subscription Upgraded —
clear, consistent casing, under 30 characters, no [CLY]_ prefix (reserved
for internal events). Pick one convention and state it in the tracking plan.
Use the built-in numeric fields. sum for monetary/numeric totals
(revenue: {key:"Purchase", sum: 9.99}), dur for durations, count for
batched occurrences. Don't smuggle numbers into segmentation strings — sum
and dur get proper aggregation and Drill math.
Timed events (startEvent/endEvent) measure durations with the start
timestamp preserved. They're cleared on identity change without merge — don't
span login boundaries with them.
Milestones and state changes are events too. "Completed onboarding", "Trial expired", "Upgraded plan" — a transaction, an action, or a milestone each deserve an event; UI minutiae (every button hover) do not. Start small and add; deleting a bad event key later doesn't remove its historic cost.
User properties
Sent as user_details. Two kinds:
- Predefined:
name, username, email, organization, phone, picture, gender, byear— use these, they get first-class UI treatment. Setting a property to""deletes it. - Custom (
custom: {...}): default limit is 20 distinct custom properties per app — treat it as a budget. A property earns its slot by being (a) stable or slowly changing and (b) useful for segmenting other data.plan_tier,signup_source,company_size_bucket: yes.last_button_clicked,connection_type: no — those are event segmentation.
Modifier operations (increment, multiply, max, min, setOnce,
push, pushUnique, pull) update server-side without read-modify-write —
use increment for lifetime counters (total_purchases), max for
high-water marks, setOnce for first-touch attribution, pushUnique for
tag lists (arrays keep max 50 elements). On Web, call
Countly.userData.save() after queuing modifiers or nothing is sent.
Every event automatically snapshots the user's properties at event time —
that's what makes "purchases by plan tier" work in Drill without adding
plan to every event. Keep properties current and events stay queryable.
Views
Views feed page/screen analytics and Flows. The single most common
integration mistake: unbounded view names. /order/1234 as a view name
exhausts the server's view limit and silently stops recording new views.
Normalize to route patterns: /order/:id, Checkout, ProductDetail.
- Web:
Countly.track_pageview()auto-tracks; pass a name for SPAs or overrideCountly.getViewNameto normalize URLs. - Mobile: prefer manual
startView("Human Name")over automatic activity/ViewController tracking — auto-collected class names (MainActivityV2Fragment) make dashboards unreadable. - View segmentation exists but reserved keys (
name, visit, start, exit, bounce, dur, segment, view, domain, platform) must not be overridden.
Crashes
Enable automatic crash reporting at init (one config flag on mobile,
Countly.track_errors() on web). Then:
- Record handled exceptions in every significant catch block
(
recordHandledException/log_error) — nonfatal errors are where quality problems show up before they become crash spikes. - Leave breadcrumbs (
addCrashBreadcrumb/add_log) at navigation and state-change points; the last 100 are attached to the next crash. - Add global crash segmentation for dimensions you'll triage by
(e.g.
plan,ab_variant). - Upload symbol files (dSYM / ProGuard mapping / JS source maps) in CI so stack traces are readable.
Consent (GDPR)
If the app needs consent, enable enforcement at init (require_consent /
setRequiresConsent(true)) — the SDK then collects nothing until per-feature
consent is given: sessions, events, views, location, crashes, users, attribution, push, star-rating, remote-config, apm, feedback, ....
The SDK does not persist consent choices — the app must store them and
re-provide on every init. Identity change without merge resets all consent.
Server limits cheat sheet (defaults, configurable server-side)
| Limit | Default |
|---|---|
| Unique event keys per app | 500 |
| Segmentation keys per event | 100 |
| Unique values per segmentation key | 1,000 |
| Elements kept from an array segmentation value | 10 |
| Custom user properties | 20 |
| Elements in a custom-property array | 50 |
| Key length (event/view/segmentation/property) | 128 chars |
| String value length (SDK truncation) | 256 chars |
| Session duration per report | 86,400 s |
| SDK event queue flush threshold | 100 events |
Hitting a limit is silent from the app's perspective — the server drops or aggregates the excess. Design under the limits; don't plan to raise them.
Verification checklist
Before calling the integration done, verify with the SDK's debug logging on
and the Countly dashboard (or /i responses — 2xx JSON with "result"):
- Sessions appear (Analytics → Overview shows the test device; session duration grows with the heartbeat).
- Every event in the tracking plan fires with the exact planned key and segmentation (Events → All Events; check segmentation dropdowns show the expected values, and nothing high-cardinality).
- Login flow: anonymous activity merges into the known user (User Profiles shows one user, not two, after login).
- Logout leaves the device ID unchanged (strategy 4) or re-enters temporary mode (strategy 3) — no fresh anonymous ID; logging into a different account switches identity without merging the two accounts' data.
- View names are bounded (no IDs/query strings in Analytics → Views).
- A forced test crash and a handled exception both appear in Crashes, symbolicated, with breadcrumbs.
- User properties appear on the profile; an event fired after a property change carries the new value in Drill.
- If consent is enforced: nothing is sent before consent; each feature starts flowing when its consent is granted.
References
- references/sdk-methods.md — exact init/method names per SDK (Web/JS, Android, iOS, Flutter, React Native). Read the section for the platform being integrated before writing code.
- references/http-api.md — raw
/iHTTP API (params, wire formats for events/user_details/crash/consent, bulk endpoint). Read for server-side/backend integrations or when debugging what the SDK sends. - Official docs: https://support.countly.com (SDK docs, Server API Reference, "Designing Events", Integration Strategy series). Prefer fetching the platform's SDK page when method signatures matter — SDKs evolve.
Gives 0 of the 12 instructions most analytics metrics skills give in ~3.3k tokens
Counted across 368 of the 369 authors here whose files we hold, read 2026-08-06
- read product marketing context before asking questionsin 18 of 368, across 12 files
- use lowercase with underscores for event namesin 16 of 368, across 6 files
- track events for decisions not vanity metricsin 15 of 368, across 5 files
- use object-action format for event namesin 15 of 368, across 8 files
- produce a tracking plan documentin 14 of 368, across 4 files
- Call RUBE_SEARCH_TOOLS first to get current schemasin 13 of 368, across 2 files
- establish consistent event naming conventions before implementingin 10 of 368, across 4 files
- Verify dimension and metric compatibility before reportingin 9 of 368, across 2 files
- Encrypt data at rest and in transitin 9 of 368, across 3 files
- use snake_case for event namesin 9 of 368, across 5 files
- monitor technical health during the testin 9 of 368, across 5 files
- use consistent property namesin 8 of 368, across 4 files
Said here and by no other author read
- design the data model before writing SDK calls
- collect dashboard questions before mapping events
- write the tracking plan into the repository
- choose a deliberate identity strategy
- normalize developer-supplied IDs before every setID call
- enable session tracking unless integration is stateless backend
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.