Mike Cann's avatar
Mike Cann
2 months ago

PostHog + Convex: Analytics, Errors & Feature Flags

If you're building on Convex and want to understand how your app behaves in production, a PostHog Convex integration gives you two complementary ways to get there. One path captures infrastructure-level signals like which functions ran and what broke, while the other captures product-level events like a user clicking a button or completing a purchase. This guide walks through both, plus a bonus path for tracking AI and LLM analytics, so you can wire PostHog into your Convex backend with a clear sense of which method fits your needs.

I've set both up, and the honest takeaway is that they solve different problems. Once you see the fork clearly, the rest of the setup is mostly mechanical. Neither path is hard on its own, but choosing the wrong one first wastes an afternoon, so the most valuable thing I can do here is help you pick correctly before you touch anything.

PostHog logs pagePostHog logs page

The Two Ways to Integrate PostHog With Convex

There are two distinct ways to connect PostHog to Convex:

  • Log streaming and error reporting integration: An infrastructure-level integration that reports which functions run, how long they take, and what errors occur.
  • The PostHog Convex component: A code-level Convex component that captures product-level events from inside your functions and gives you access to feature flags.

Which one you reach for depends entirely on what you're trying to see. If you want observability into your backend's health and behavior, the log streaming integration answers that. If you want to know what your users are doing, the PostHog component is the right tool. Establishing this fork up front saves you from setting up the wrong path and wondering why the data looks different from what you expected.

These two aren't mutually exclusive, and most production apps end up running both. The point of separating them here is not to make you choose one forever, but to make sure you understand which problem each one solves before you wire it in. When the log stream tells you a function is throwing errors, that's a different signal from the component telling you that users stopped completing a checkout flow, and you want both signals reaching PostHog so you can correlate them.

One practical constraint shapes this decision. Log streaming requires a paid Convex plan, whereas the PostHog Convex component doesn't. So if you're on a free plan and want to start capturing product events today, the component is your entry point. That single constraint often decides the order of operations for new projects, because there's no reason to wait on a paid plan when the component can start recording meaningful events from inside your mutations immediately.

Setting Up PostHog Log Streaming and Error Reporting

You set up PostHog log streaming and error reporting entirely from the Convex dashboard, with no code required. You enable the integration, paste in a PostHog project token, and then verify that logs and errors arrive in your PostHog database. This is the fastest win because you can validate it works before touching your codebase, which means you get a confidence boost early without committing any changes to your functions.

This path appeals as a starting point because it asks nothing of your application logic. You're not adding capture calls, reasoning about which events matter, or refactoring mutations. You're flipping a switch in a dashboard and watching data flow, so the feedback loop is tight and the risk is close to zero.

Enabling the Integration in the Convex Dashboard

  1. Open your dashboard by running npx convex dashboard in your terminal.
  2. Navigate to Settings, then Integrations.
  3. Look for the plus icon to enable PostHog log streaming.
  4. When prompted, enter a PostHog project token to complete the connection.

This is the entire interface for the infrastructure-level path, which is a deliberate design choice. Because the integration lives in the dashboard rather than in your code, you can enable, disable, or reconfigure it without redeploying anything, so iterating on what you stream costs nothing in deployment time.

Adding Your PostHog Project Token

Grab the project token from your PostHog dashboard and paste it into the Convex integration field. The same token enables error tracking, so you don't need a second credential for that. Once the token is saved, the connection is live.

This single-token arrangement is convenient because error reporting and log streaming are often treated as separate setups in other stacks, each demanding its own configuration. Here they share one credential, which means enabling logs automatically lets you catch errors without any extra wiring, and that reduces the number of places a misconfiguration can hide.

Verifying Logs and Errors Arrive

Trigger some activity in your app to generate function executions. In PostHog, confirm that the function executed topic appears with the function path attached, which tells you the log stream is flowing. To verify error tracking, throw an error and check that it surfaces in PostHog with the originating file and line number, so you can trace exactly where a failure happened.

That file and line number is the detail worth dwelling on. An error report that only tells you something broke is mildly useful, whereas one that points you to the exact file and line turns a debugging session into a direct lookup. I rely on this more than I expected to, because it collapses the gap between noticing a failure in production and locating its source in the codebase.

Installing the PostHog Convex Component

The PostHog Convex component lets you capture events from inside your Convex functions rather than only from the client, and it gives you access to feature flags. You install it, set two environment variables, and then add capture calls inside your mutations, all of which PostHog's Convex library docs walk through in detail. This is the granular, product-level path that most analytics use cases need.

Capturing events server-side rather than client-side matters more than it first appears. Client-side analytics can be blocked, dropped, or skipped when a browser tab closes before the event fires, whereas a capture call running inside a mutation executes as part of the transaction your backend already committed to. That gives you a more reliable record of what happened, which is exactly what you want when the events feed product decisions.

Setting Environment Variables

Set your PostHog API key and host using npx convex env set. You can manage these the same way you handle any other Convex environment variables, keeping the credentials out of your code and available to your functions at runtime. With the key and host in place, the component can talk to PostHog.

Keeping the key and host as environment variables rather than hardcoded values is the right default for a credential that differs between your development and production deployments. Because Convex scopes environment variables per deployment, you can point your local environment at one PostHog project and production at another without changing a line of code, which keeps test events from polluting your production analytics.

Capturing Events Inside Mutations

Add a PostHog capture call inside a mutation where a meaningful action happens, such as a user creating a record or completing a step. Run the mutation, then open PostHog's product analytics view and confirm the event landed in your PostHog database. Because the capture runs server-side from inside the mutation, you get a reliable record of what happened in your backend rather than depending on the client to report it.

The judgment call here is which actions count as meaningful, and that's worth thinking about before you scatter capture calls everywhere. A capture call on every mutation produces noise that buries the signal, whereas a capture call on the handful of actions that map to real product outcomes gives you a dashboard you can reason about. I start narrow and add events as questions come up, since it's easier to add instrumentation than to clean up an event stream that records everything.

Using PostHog Feature Flags in Convex

You use PostHog feature flags in a Convex app by writing a featureFlags.ts action that calls the component to check whether a flag is enabled, then consuming that result through a useFeatureFlag React hook. The hook feeds a component that conditionally renders something, such as a welcome banner. You create and toggle the flag in the PostHog dashboard and watch the UI respond.

The flow is straightforward:

  1. Create a flag in PostHog.
  2. Wire the action to check it.
  3. Surface the result through the hook.
  4. Gate your UI on the returned value.

Flip the flag in the PostHog dashboard and the banner appears or disappears. The value of this pattern is that it moves the decision about who sees a feature out of your code and into a dashboard, so you can roll a feature out gradually or kill it instantly without shipping a new deploy.

There's one current trade-off worth being honest about. Because the flag check is implemented as an action rather than a query, the page has to be refreshed to reflect a change. A query would let Convex's reactive query model sync the UI automatically as the flag value changes, without a manual refresh. This is a current limitation of the component pattern rather than a flaw, and I expect it will improve in future versions, so for now plan on a refresh when you toggle a flag during testing.

It's worth being precise about where this matters and where it doesn't. During development and QA, the refresh is a mild annoyance that you'll notice every time you flip a flag to test both states. In production it matters far less, because a user who lands on the page after you've rolled out a flag sees the correct state on load, and the absence of live reactivity rarely changes their experience. So weigh the limitation against your testing workflow rather than against your end users, since that's where it lands.

Tracking AI and LLM Analytics With PostHog

You can track AI and LLM analytics in Convex with PostHog by installing the PostHog AI SDK alongside OpenTelemetry dependencies, then routing your model traces through PostHog. You create a provider, a span processor, and a PostHog trace exporter, register a global trace provider, and enable experimental_telemetry on the AI SDK's text generation call. This captures traces, latency, token usage, and cost.

The setup follows a clear sequence:

  1. Install the PostHog AI SDK and OpenTelemetry packages.
  2. Create the provider and span processor.
  3. Build the PostHog trace exporter.
  4. Register the global trace provider.
  5. Enable telemetry on the generation call.

Once that's wired, every text generation request flows its telemetry into your PostHog database, which means the data starts accumulating the moment your next request runs.

This path takes more setup than the other two, and it's worth being candid about that. You're installing several packages and assembling a small chain of OpenTelemetry primitives rather than flipping a dashboard switch or adding a single capture call. The payoff is that token usage and cost are exactly the numbers that quietly grow until someone notices the bill, so instrumenting them early gives you a running view of spend before it becomes a surprise rather than after.

If you're using the Convex Agent component, the approach is parallel but the attachment point shifts. Because the Convex Agent uses the AI SDK under the hood, you attach telemetry to the thread's text generation rather than to the raw SDK call. Either way, you end up with traces, latency, token usage, and cost in PostHog, which gives teams building AI features a direct view into model behavior and spend that's easy to miss otherwise.

The attachment point matters because the Agent component manages the generation call for you, so there's no raw SDK call sitting in your code to decorate with telemetry. You hook into the thread's generation step instead, and the telemetry chain you built behaves identically from PostHog's perspective. That consistency is useful, because it means the dashboards you set up for raw SDK calls keep working when you migrate a feature onto the Agent component.

Frequently Asked Questions

Q: Does PostHog log streaming require a paid Convex plan? A: Yes. Log streaming and error reporting require a paid Convex plan. The PostHog Convex component doesn't, so if you're on a free plan you can still capture product events and use feature flags through the component. This is the single constraint most likely to decide which path you start with, because a free-plan project that wants analytics today has to begin with the component regardless of what its backend observability needs eventually become.

Q: What's the difference between the log streaming integration and the PostHog component? A: The log streaming integration is infrastructure-level observability, reporting which functions ran, how long they took, and what errors occurred. The PostHog component is product-level event capture, recording what users do from inside your Convex functions. Use log streaming for backend health, and use the component for product analytics. Most production apps run both, since the function-level signals and the user-level events answer different questions and you usually want to correlate the two.

Q: How do you use PostHog feature flags in a Convex app? A: You write an action that calls the PostHog component to check whether a flag is enabled, then surface the result through a useFeatureFlag React hook that gates your UI. Because the check runs as an action rather than a query, you currently need to refresh the page to see a toggled flag take effect. That refresh is a testing-workflow annoyance more than a production concern, since end users land on the page and see the correct flag state on load.

Q: Can you track LLM/AI usage in Convex with PostHog? A: Yes. You install the PostHog AI SDK and OpenTelemetry, create a provider and trace exporter, register a global trace provider, and enable experimental_telemetry on your text generation call. This captures traces, latency, token usage, and cost, with a parallel setup available for the Convex Agent component. The setup takes more effort than the other two integrations, but token and cost data are the signals that go unmeasured most often, so the extra work pays off as soon as your AI usage starts to scale.

Choosing Your Starting Point With the PostHog Convex Integration

A complete PostHog Convex integration is really two integrations that work together: dashboard-level log streaming for backend observability, and the PostHog Convex component for product events, feature flags, and AI analytics. If you're on a paid plan and want infrastructure visibility fast, start with log streaming since it needs no code. If you want to track what users do or experiment with feature flags, install the component and capture events from inside your mutations. The AI analytics path is worth the extra setup for any team running LLM features on Convex, because traces, latency, tokens, and cost are exactly the signals that go unmeasured otherwise.

Reduced to one rule, the advice is to start with whichever path answers the question you're actually asking right now, because both are quick to add and you'll almost certainly end up with both. A free-plan project asking what users do starts with the component. A paid-plan team asking why the backend is slow starts with log streaming. A team shipping an AI feature adds the telemetry chain the moment cost becomes a question worth tracking. None of these choices locks you out of the others, so the worst outcome of picking wrong is a short detour rather than a rebuild.

Explore the PostHog component in the Convex Components directory to see its full capabilities, and if you're new to the platform, get started building on Convex to set up the backend these integrations plug into.

Build in minutes, scale forever.

Convex is the backend platform with everything you need to build your full-stack AI project. Cloud functions, a database, file storage, scheduling, workflow, vector search, and realtime updates fit together seamlessly.

Get started