How to add analytics to a Convex app in 10 minutes

Dan Cleary
·

I've been building on Convex for a while. Every new project hit the same wall when it was time to add analytics.

Two options, both bad:

  1. Roll your own events table in Convex and build a dashboard.
  2. Bolt on PostHog or Amplitude. Overkill for what I actually wanted.

I just wanted something simple. So I built Convalytics. This post is how I wire it up on a new Convex app. Ten minutes start to finish.

What you'll have when this is done

The fastest path: paste a prompt

If you don't want to read the rest of this, the homepage at convalytics.dev has a Copy agent prompt button. Copy it, paste it into Claude Code, Cursor, or any AI coding agent, and it does the install for you. Page views, the Convex component, the analytics singleton, and the first event get wired up while you make coffee.

Or just copy the prompt right here:

Show the prompt

Add Convalytics analytics to this Convex project.

Before starting, ask me: do I want (A) web analytics only — automatic page views,
sessions, bounce rate, referrers — or (B) web analytics + product analytics
for signups, payments, feature usage, etc.?

## Option A — Web analytics only

Run: npx convalytics init

This provisions a project and adds a script tag to index.html. That's it — page
views start flowing immediately. No SDK or component needed.

For non-Vite projects (Next.js, Astro, etc.), the CLI prints the script tag to
add manually to .

Share the claim URL from the output with me.

## Option B — Web analytics + product analytics

Run: npx convalytics init

Same as above, plus it installs the Convex component for tracking custom events
from mutations and actions.

After install, read convex/schema.ts and all files in convex/. Identify every
mutation and action that represents a meaningful user action. Propose a tracking
plan as a numbered list:

  1. event_name — file.ts → functionName — props: { key }

Wait for me to approve, then instrument each with:

  import { analytics } from "./analytics";
  await analytics.track(ctx, { name: "event_name", userId: String(userId), props: { key: "value" } });

Then verify: npx convalytics verify

Share the claim URL from the output with me.

The rest of this post is the manual reference, in case you want to know exactly what the agent's about to do (or you don't use a coding agent).

1. Provision a project

npx convalytics init

That hits POST /api/provision, gets a write key back, and inlines it into convex/analytics.ts (the write key is a public ingest identifier, safe to commit). The project is unclaimed. You can link it to a Convalytics account later from the URL the CLI prints. No signup needed first.

2. Drop in the browser script

Page views are free and uncounted against quota. The script is the easiest path:

<script src="https://api.convalytics.dev/script.js?key=YOUR_WRITE_KEY" defer></script>

That captures every route change automatically. Convalytics hooks pushState and popstate, so SPAs work without extra config. Open your dashboard, click around your app, and hits start landing.

3. Install the Convex backend component

Custom events from server functions are the more interesting ones. You know who triggered them, you can attach Convex IDs as props, and the write key never touches the browser.

If you used the agent prompt above, this is already done. The agent reads your convex/schema.ts, scans every mutation and action, and proposes a tracking plan as a numbered list before instrumenting anything. You either approve, edit, or just say "track everything that touches the users table."

npm i convalytics-dev

Register it in convex/convex.config.ts:

import { defineApp } from "convex/server";
import analytics from "convalytics-dev/convex.config";

const app = defineApp();
app.use(analytics);
export default app;

Create a singleton in convex/analytics.ts with the write key inlined:

import { components } from "./_generated/api";
import { Convalytics } from "convalytics-dev";

export const analytics = new Convalytics(components.convalytics, {
  writeKey: "wk_...",
});

The write key is a public ingest identifier — safe to commit. No environment variable is required: the component auto-detects the deployment (dev / prod) from Convex's injected CONVEX_CLOUD_URL at runtime.

4. Track an event from a mutation

In any Convex mutation, action, or scheduled function:

import { mutation } from "./_generated/server";
import { analytics } from "./analytics";

export const completeSignup = mutation({
  args: { /* ... */ },
  handler: async (ctx, args) => {
    const userId = await ctx.db.insert("users", { /* ... */ });

    await analytics.track(ctx, {
      name: "signup_completed",
      userId: String(userId),
      props: { plan: "free", source: args.referrer },
    });

    return userId;
  },
});

That's the whole integration. track() schedules a fire-and-forget delivery from your Convex deployment — it never throws and never blocks the caller. Your write key never reaches the browser.

5. Identify users so the dashboard shows names

By default an event row stores a userId like j5742w.... Useful for joining, useless for skimming the User Activity tab. Pass the user's email and name and the dashboard shows the actual person, lets you filter by email, and lets the MCP tool answer "how is alice@acme.com using the app?" instead of asking which Convex ID belongs to Alice.

Server-side, pass userEmail and userName to track():

await analytics.track(ctx, {
  name: "feature_used",
  userId: String(user._id),
  userEmail: user.email,
  userName: user.name,
  props: { feature: "export_csv" },
});

If you want browser-emitted events tied to the same user, call identify() once after sign-in:

window.convalytics.identify(userId, { email, name });

And convalytics.reset() on logout so the next visitor doesn't inherit the previous session.

6. Bonus: query it from Claude

Convalytics ships an MCP server. You can ask Claude Desktop or Claude Code things like "how many signups this week on my Convex app?" and it answers.

claude mcp add --transport http convalytics https://api.convalytics.dev/mcp \
  --header "Authorization: Bearer $CONVALYTICS_TOKEN"

Generate the token at convalytics.dev/tokens. The MCP endpoint requires the Solo plan ($29/mo) or higher. Dashboard and ingest API stay free.

What you didn't have to do

Ten minutes to live analytics. Two of those were npm install.

What's the next thing you'd want it to do? Tell me on X.