Platform
Hosting & deploysPush to a branch, get a live linkDatabaseReal Postgres, one per appSign-inEmail and Google, users includedFile storageUploads, photos and documentsAI gatewayEvery model, with a hard spend cap
Resources
DocsGuides written for people who are newFor AI agentsClaude Code, Cursor, MCP, JSON CLICompareCamplax beside the stack it replacesPricingFrom $25/month plus prepaid creditsSupportA person answers, not a queue botPoliciesTerms, privacy and how we handle data Get started
Open in ChatGPT Open in Claude Last updated 2026-11-03

Analytics

First-party product analytics that are already wired into your app — pageviews, web vitals, people, funnels, retention cohorts, dashboards and metric alerts.

Analytics on Camplax is first-party: the tracker loads from your app's own domain, events post to your own domain, and the data lands in your project. There is no third-party script and nothing sets a cookie — visitor and session ids live in localStorage, and IPs are truncated before they are stored. For most apps that is enough to run without a consent banner, though that call is yours.

It is already on

You do not install anything. The runtime injects a small script tag into your app's HTML and serves the tracker same-origin:

<script src="/camplax/cx.js" data-proxy="same-origin" data-api="/camplax" defer></script>

The browser posts to /camplax/v1/analytics/i on your own domain; the runtime forwards it to the platform with your project's ingest key, which is also in your app's environment as CAMPLAX_ANALYTICS_KEY. The key never appears in your HTML — it lives on the server side of the proxy. Bots and headless browsers are dropped before they ever count as a visit.

What you get without code

  • $pageview on every navigation — including SPA route changes, deduplicated so the same URL does not count twice — with path, referrer, UTM tags, browser, device and screen size attached. Page and referrer addresses are kept without their query string or fragment, so a reset token or email in a link is never recorded.
  • $web_vital events for LCP, CLS, INP, TTFB and load time — the console's Speed page.
  • Errors — window.onerror and unhandled rejections are captured and sent to the error tracker, not here.
  • A live stream — the Events page shows events as they arrive.

Track your own events

The snippet exposes window.cx.analytics:

window.cx.analytics.track("added_to_cart", { sku: "pro-plan", price: 20 });

window.cx.analytics.page("Pricing");            // a named pageview
window.cx.analytics.identify("user_123", { plan: "pro" });  // attach the account
window.cx.analytics.reset();                  // on sign-out — new visitor ids
window.cx.analytics.flush();                  // force-send the queued batch

identify ties the anonymous visitor to a user id — one person keeps counting as one across sign-in — and emits a $identify event with the traits. Events batch in the browser and flush every two seconds or at ten events, whichever comes first.

The raw endpoint

Anything that can POST can send events — a backend, a script, another app:

curl https://camplax.dev/v1/analytics/i \
  -H "x-camplax-analytics-key: $CAMPLAX_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sentAt": 1736400000000,
    "batch": [{
      "event": "added_to_cart",
      "distinctId": "anon_9f2…",
      "userId": "user_123",
      "properties": { "sku": "pro-plan" },
      "path": "/pricing"
    }]
  }'

Send the cak_live_… ingest key as x-camplax-analytics-key or Authorization: Bearer. Each event needs event (letters, $, _, ., /, : or -, up to 200 chars) and distinctId; id, timestamp, sessionId, path, environment and userId are optional. A batch holds 1–50 events in a 256 KB body; replays are deduplicated on the client id. Event properties are scrubbed before storage — keys that look like secrets (password, token, authorization, …) are dropped and secret-looking values are redacted.

The endpoint allows 60 posts a minute per IP and 500 per key. A 429 with analytics_capped means the monthly cap is spent — the app stays up, the events just stop counting.

The console

The Analytics page is six sections in its own sidebar:

  • Traffic — visitors, pageviews, top pages and referrers over 24 hours, 7 days or 30 days.
  • Speed — the web-vitals percentiles per page, over the same range.
  • Events — the live stream as events arrive, plus the People roster: a row per visitor and a per-person timeline once identify has run.
  • Funnels — saved 2–5 step funnels with a conversion window (up to 90 days), run on demand.
  • Retention — a cohort table for production, preview or all environments: people grouped by the day or week they were first seen, and the share who came back in each later period.
  • Flags — feature flags, each with its on/off, targeting and rollout share.

The collection switches, metric alerts and the SDK snippet sit behind Settings on every page, and exporting the newest 1,000 events as CSV is in the ⋯ menu on Traffic and Events. Dashboards, ad-hoc insights and ingest-key rotation are API-only for now.

Retention cohorts

GET /v1/projects/:slug/analytics/retention/cohorts?period=week&periods=8 returns one row per cohort — everyone first seen in that day or week — with size, returned[k] (how many were active k periods later; k = 0 is the period they joined) and the same as rates[k]. period is day (up to 30) or week (up to 12); environment is production (default), preview or all; returnEvent=signup counts a return only when the person did that event. "First seen" comes from the person record, so someone who visited before the window is not counted as new. Cohorts can only look back as far as your plan keeps history: 7 days on Indie, 14 on Pro, 30 on Scale.

Dashboards

A dashboard is a named page of tiles; each tile is a saved insight, not a saved number, so it is always current and costs nothing while nobody looks. Three kinds of tile:

InsightShape
Trend{ kind: "trend", event, math: "total" | "unique", days: 1–90 } — per-day counts and a total
Funnel{ kind: "funnel", funnelId } — one of your saved funnels, step counts and conversion
Retention{ kind: "retention", period: "day" | "week", periods: 2–12 } — the cohort table

Every insight also takes environment. Up to 10 dashboards per project and 12 tiles each. API: GET/POST /analytics/dashboards, PATCH/DELETE /analytics/dashboards/:id, POST /analytics/dashboards/:id/tiles, DELETE /analytics/dashboards/:id/tiles/:tileId, PUT /analytics/dashboards/:id/order { tileIds }, and POST /analytics/insights/run { insight } to run any insight once.

Metric alerts

"Tell the team when signup conversion drops 20%" — a metric alert watches one number and posts to your alert channels (Slack, Discord or a signed webhook under Settings → Alerts, with the Analytics alert event switched on) and emails the project's alert recipients — the same list uptime uses — when it crosses the line:

MetricWhat is measured over windowHours (1–168)
event_countHow many times event happened
unique_usersHow many people did anything (or event)
funnel_conversionShare of people who finished a saved funnel, in %

Channels created before metric alerts existed do not hear them until you tick Analytics alert on the channel; the Settings sheet says so when no channel does.

The condition is either a fixed line — { op: "below" \| "above", value } — or a change against the window just before — { op: "drops_by" \| "rises_by", percent }. Alerts are checked once an hour; a firing alert posts once and stays quiet for cooldownHours (default 24) while it keeps breaching, and goes back to normal on its own when the number recovers. Up to 10 alerts per project. API: GET/POST /analytics/alerts, PATCH /analytics/alerts/:id { enabled?, name? }, DELETE /analytics/alerts/:id. GET also returns channels — how many enabled channels hear the event; if it is 0, an alert has nowhere to go.

Settings

The Settings sheet on the same page holds four switches:

SettingDefaultWhat it does
autoPageviewsonThe tracker records a $pageview on every load and navigation. Off: only the page views your app sends with cx.analytics.page() count
excludePreviewonDrop events from preview deploys
autocaptureoffRecord every click on a link or button as $autocapture: its tag, id, visible text (80 characters) and, for links, the path — never a field's value or a query string. Put data-cx-no-capture on an element to leave it and everything inside it out
pausedoffAccept nothing until re-enabled

The sheet also shows retentionDays — how many days of events and visitors are kept. It is your plan's history window (7 on Indie, 14 on Pro, 30 on Scale), not a setting: PATCH /v1/projects/:slug/analytics/settings with any other retentionDays is refused with 400 retention_follows_plan.

Each switch is enforced when events arrive, so turning one off takes effect at once; the tracker also learns the setting from each answer and stops sending from the next page load.

Rotating the ingest key is an API call, not a console button — POST /v1/projects/:slug/analytics/rotate-key with a console session. The response shows the new cak_live_… once.

Limits

  • 250,000 production events per project per calendar month, plus a separate 10,000 for preview.
  • History follows the plan: events and visitors are kept 7 days on Indie, 14 on Pro and 30 on Scale, and older ones are removed by a sweep that runs every minute. Moving to a smaller plan removes what is older than its window on the next pass.
  • $probe events — written by camplax probe when it checks your project — are never shown in the console.
  • Preview traffic stamps its environment server-side; a client cannot claim to be production.

Under the hood: events are stored by Camplax, not in your app's database, and nothing is written into your database; the tracker is a single-file bundle with no dependencies.

Camplax

Hosting, database, sign-in and storage in one project. One login, one bill.

Platform
More
Legal
Company
© 2026 Camplax From $25 / month · credits stop at zero