@posthog/convex 2.0.27 → 2.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -324,3 +324,30 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
324
324
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
325
325
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
326
326
  SOFTWARE.
327
+
328
+ ---
329
+
330
+ Some files in this codebase contain code from MCPCat/mcpcat-typescript-sdk.
331
+ In such cases it is explicitly stated in the file header. This license only applies to the relevant code in such cases.
332
+
333
+ MIT License
334
+
335
+ Copyright (c) 2025 MCPcat
336
+
337
+ Permission is hereby granted, free of charge, to any person obtaining a copy
338
+ of this software and associated documentation files (the "Software"), to deal
339
+ in the Software without restriction, including without limitation the rights
340
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
341
+ copies of the Software, and to permit persons to whom the Software is
342
+ furnished to do so, subject to the following conditions:
343
+
344
+ The above copyright notice and this permission notice shall be included in all
345
+ copies or substantial portions of the Software.
346
+
347
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
348
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
349
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
350
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
351
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
352
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
353
+ SOFTWARE.
package/README.md CHANGED
@@ -1,414 +1,14 @@
1
- <p align="center">
2
- <img alt="@posthog/convex" src="https://res.cloudinary.com/dmukukwp6/image/upload/q_auto,f_auto/posthog_convex_c017c269f8.png" width="500">
3
- </p>
4
-
5
- <h1 align="center">@posthog/convex</h1>
6
-
7
- <p align="center">
8
- PostHog analytics and feature flags for your Convex backend.
9
- </p>
1
+ # PostHog Convex package
10
2
 
11
3
  <p align="center">
12
4
  <a href="https://www.npmjs.com/package/@posthog/convex"><img src="https://badge.fury.io/js/@posthog%2Fconvex.svg" alt="npm version"></a>
13
5
  <a href="https://www.convex.dev/components/posthog/convex"><img src="https://www.convex.dev/components/badge/posthog/convex" alt="Convex Component"></a>
14
6
  </p>
15
7
 
16
- ## 🦔 What is this?
17
-
18
- The official [PostHog](https://posthog.com) component for [Convex](https://convex.dev). Capture events, identify users, manage groups, and evaluate feature flags — all from your queries, mutations, and actions.
19
-
20
- Found a bug? Feature request? [File it here](https://github.com/PostHog/posthog-js/issues).
21
-
22
- ## 🚀 Quick Start
23
-
24
- Install the package (requires Convex 1.39 or newer):
25
-
26
- ```sh
27
- pnpm add @posthog/convex
28
- ```
29
-
30
- Register the component in your `convex/convex.config.ts` and forward the env vars from your app to the component:
31
-
32
- ```ts
33
- // convex/convex.config.ts
34
- import { defineApp } from "convex/server";
35
- import { v } from "convex/values";
36
- import posthog from "@posthog/convex/convex.config.js";
37
-
38
- const app = defineApp({
39
- env: {
40
- // Required. PostHog project token (`phc_…`) — used to send events and evaluate flags remotely.
41
- POSTHOG_PROJECT_TOKEN: v.string(),
42
- // Optional. PostHog host. Defaults to `https://us.i.posthog.com`; use `https://eu.i.posthog.com` for EU Cloud or your self-hosted URL.
43
- POSTHOG_HOST: v.optional(v.string()),
44
- // Optional. A feature flags secure API key (`phs_…`, recommended) or personal API key (`phx_…`). Setting it enables local feature flag evaluation.
45
- POSTHOG_PERSONAL_API_KEY: v.optional(v.string()),
46
- // Optional. Cron interval (seconds) for refreshing flag definitions. Defaults to 60. Raise it on free-tier dev deployments to reduce function-call usage.
47
- POSTHOG_FLAGS_POLLING_INTERVAL_SECONDS: v.optional(v.string()),
48
- },
49
- });
50
-
51
- app.use(posthog, {
52
- env: {
53
- POSTHOG_PROJECT_TOKEN: app.env.POSTHOG_PROJECT_TOKEN,
54
- POSTHOG_HOST: app.env.POSTHOG_HOST,
55
- POSTHOG_PERSONAL_API_KEY: app.env.POSTHOG_PERSONAL_API_KEY,
56
- POSTHOG_FLAGS_POLLING_INTERVAL_SECONDS: app.env.POSTHOG_FLAGS_POLLING_INTERVAL_SECONDS,
57
- },
58
- });
59
-
60
- export default app;
61
- ```
62
-
63
- Set your PostHog credentials on your Convex deployment:
64
-
65
- ```sh
66
- npx convex env set POSTHOG_PROJECT_TOKEN phc_your_project_token
67
- npx convex env set POSTHOG_HOST https://us.i.posthog.com
68
- ```
69
-
70
- To enable local feature flag evaluation, also set a [feature flags secure API key](https://posthog.com/docs/feature-flags/local-evaluation#step-1-find-your-feature-flags-secure-api-key) (`phs_…`) with read access to feature flags:
71
-
72
- ```sh
73
- npx convex env set POSTHOG_PERSONAL_API_KEY phs_your_feature_flags_secure_api_key
74
- ```
75
-
76
- > Personal API keys (`phx_…`) also still work for local evaluation, but PostHog recommends the project-scoped feature flags secure API key going forward. Setting this env var is what flips on local evaluation — the component's built-in refresh cron starts populating flag definitions on the next tick, no redeploy needed.
77
-
78
- Create a `convex/posthog.ts` file to initialize the client. Credentials live on the component, so this file is just for callbacks (identify, beforeSend):
79
-
80
- ```ts
81
- // convex/posthog.ts
82
- import { PostHog } from "@posthog/convex";
83
- import { components } from "./_generated/api";
84
-
85
- export const posthog = new PostHog(components.posthog);
86
- ```
87
-
88
- That's the whole setup — feature flag methods will start returning live values on the next cron tick. The component refreshes flag definitions once a minute by default when `POSTHOG_PERSONAL_API_KEY` is set. To tune the cadence (e.g. raise it to `300` for a free-tier dev deployment), set `POSTHOG_FLAGS_POLLING_INTERVAL_SECONDS` and redeploy:
89
-
90
- ```sh
91
- npx convex env set POSTHOG_FLAGS_POLLING_INTERVAL_SECONDS 300
92
- ```
93
-
94
- If you call a local-eval method (`getFeatureFlag`, `isFeatureEnabled`, …) without `POSTHOG_PERSONAL_API_KEY` configured, the client throws with a pointer to the remote `evaluateFlag` / `evaluateFlagPayload` / `evaluateAllFlags` methods. While the first cron tick is still in flight (PAK is set but no definitions are cached yet) the local methods return `undefined` so your fallback path keeps working.
95
-
96
- Need to force a refresh between cron ticks (e.g. just after creating a flag in development)? Call `posthog.reloadFeatureFlags(ctx)` from an action — same name and shape as `posthog-node`.
97
-
98
- ## 📊 Capturing Events
99
-
100
- Import `posthog` from your setup file and call methods directly:
101
-
102
- ```ts
103
- // convex/myFunctions.ts
104
- import { posthog } from "./posthog";
105
- import { mutation } from "./_generated/server";
106
- import { v } from "convex/values";
107
-
108
- export const createUser = mutation({
109
- args: { email: v.string() },
110
- handler: async (ctx, args) => {
111
- const userId = await ctx.db.insert("users", { email: args.email });
112
-
113
- await posthog.capture(ctx, {
114
- distinctId: userId,
115
- event: "user_created",
116
- properties: { email: args.email },
117
- });
118
-
119
- return userId;
120
- },
121
- });
122
- ```
123
-
124
- ### capture
125
-
126
- Capture an event. Works in mutations and actions.
127
-
128
- ```ts
129
- await posthog.capture(ctx, {
130
- distinctId: "user_123",
131
- event: "purchase_completed",
132
- properties: { amount: 99.99, currency: "USD" },
133
- groups: { company: "acme-corp" },
134
- });
135
- ```
136
-
137
- Options: `distinctId`, `event`, `properties`, `groups`, `sendFeatureFlags`, `timestamp`, `uuid`, `disableGeoip`.
138
-
139
- ### identify
140
-
141
- Set user properties.
142
-
143
- ```ts
144
- await posthog.identify(ctx, {
145
- distinctId: "user_123",
146
- properties: { name: "Jane Doe", plan: "pro" },
147
- });
148
- ```
149
-
150
- ### groupIdentify
151
-
152
- Set group properties.
153
-
154
- ```ts
155
- await posthog.groupIdentify(ctx, {
156
- groupType: "company",
157
- groupKey: "acme-corp",
158
- properties: { industry: "Technology", employees: 500 },
159
- });
160
- ```
161
-
162
- ### alias
163
-
164
- Link two distinct IDs.
165
-
166
- ```ts
167
- await posthog.alias(ctx, {
168
- distinctId: "user_123",
169
- alias: "anonymous_456",
170
- });
171
- ```
172
-
173
- ### captureException
174
-
175
- Send an exception to PostHog's error tracking pipeline. Accepts an `Error`, a string, or any object with a `message` field.
176
-
177
- ```ts
178
- try {
179
- await chargeCard(...);
180
- } catch (error) {
181
- await posthog.captureException(ctx, {
182
- error,
183
- distinctId: "user_123",
184
- additionalProperties: { plan: "pro" },
185
- });
186
- throw error;
187
- }
188
- ```
189
-
190
- If you'd rather have **every** uncaught error from your Convex deployment forwarded to PostHog automatically — including ones you didn't explicitly wrap — wire up Convex's first-party PostHog exception reporting integration from the Convex dashboard. Setup lives at [docs.convex.dev/production/integrations/exception-reporting#configuring-posthog-error-tracking](https://docs.convex.dev/production/integrations/exception-reporting#configuring-posthog-error-tracking). Use `captureException` here for cases where you want explicit control (e.g. attaching custom `additionalProperties`); use the Convex-side integration for catch-all coverage.
191
-
192
- All of the above methods schedule the PostHog API call asynchronously via `ctx.scheduler.runAfter`, so they return immediately without blocking your mutation or action.
193
-
194
- ## 🚩 Feature Flags
195
-
196
- Two evaluation paths, pick the one that fits the flag:
197
-
198
- - **Local** (`getFeatureFlag`, `isFeatureEnabled`, …) — evaluates against definitions cached by the cron. Works in **queries, mutations, and actions**, no per-call network round-trip, reactive (a query reading a flag re-runs when definitions change). Requires `POSTHOG_PERSONAL_API_KEY`. Can't handle every flag — see [the limitations](#local-evaluation--limitations) below.
199
- - **Remote** (`evaluateFlag`, `evaluateFlagPayload`, `evaluateAllFlags`) — hits PostHog's `/flags` endpoint directly. Action-context only, no `personalApiKey` needed, handles every flag.
200
-
201
- The local methods are documented first; remote is at the bottom of this section.
202
-
203
- ### getFeatureFlag
204
-
205
- Get a flag's value.
206
-
207
- ```ts
208
- import { posthog } from "./posthog";
209
- import { query } from "./_generated/server";
210
- import { v } from "convex/values";
211
-
212
- export const getDiscount = query({
213
- args: { userId: v.string() },
214
- handler: async (ctx, args) => {
215
- const flag = await posthog.getFeatureFlag(ctx, {
216
- key: "discount-campaign",
217
- distinctId: args.userId,
218
- });
219
-
220
- if (flag === "variant-a") {
221
- return { discount: 20 };
222
- }
223
- return { discount: 0 };
224
- },
225
- });
226
- ```
227
-
228
- ### isFeatureEnabled
229
-
230
- Check if a flag is enabled.
231
-
232
- ```ts
233
- const enabled = await posthog.isFeatureEnabled(ctx, {
234
- key: "new-onboarding",
235
- distinctId: "user_123",
236
- });
237
- ```
238
-
239
- ### getFeatureFlagPayload
240
-
241
- Get a flag's JSON payload.
242
-
243
- ```ts
244
- const payload = await posthog.getFeatureFlagPayload(ctx, {
245
- key: "pricing-config",
246
- distinctId: "user_123",
247
- });
248
- ```
249
-
250
- ### getFeatureFlagResult
251
-
252
- Get a flag's value and payload in one call.
253
-
254
- ```ts
255
- const result = await posthog.getFeatureFlagResult(ctx, {
256
- key: "experiment-flag",
257
- distinctId: "user_123",
258
- });
259
- if (result) {
260
- console.log(result.enabled, result.variant, result.payload);
261
- }
262
- ```
263
-
264
- ### getAllFlags
265
-
266
- Get all flag values for a user.
267
-
268
- ```ts
269
- const flags = await posthog.getAllFlags(ctx, {
270
- distinctId: "user_123",
271
- });
272
- ```
273
-
274
- ### getAllFlagsAndPayloads
275
-
276
- Get all flags and their payloads.
277
-
278
- ```ts
279
- const { featureFlags, featureFlagPayloads } =
280
- await posthog.getAllFlagsAndPayloads(ctx, {
281
- distinctId: "user_123",
282
- });
283
- ```
284
-
285
- All feature flag methods accept optional `groups`, `personProperties`, `groupProperties`, and `disableGeoip` options. `getAllFlags` and `getAllFlagsAndPayloads` also accept `flagKeys` to filter which flags to evaluate.
286
-
287
- ### Local evaluation — limitations
288
-
289
- Local eval can't reach a verdict for every flag, and for those this component will return `null`. The cases:
290
-
291
- - **Experience continuity flags.** Flags with [persist across authentication steps](https://posthog.com/docs/feature-flags/creating-feature-flags#persisting-feature-flags-across-authentication-steps) need server-side anon→identified tracking and aren't included in local eval.
292
- - **Static cohorts.** Cohort membership for static cohorts lives only on the server.
293
- - **Properties not passed in.** Local eval can only see what you give it. If a flag targets `email` or `$browser_version` and you don't pass those in `personProperties`, it can't resolve.
294
- - **Cohorts that don't fit the local-eval shape.** Cohorts with variant overrides, non-person properties, more than one cohort in the same flag definition, nested AND/OR filters, or grouped with other conditions can't be translated for local eval. See [the PostHog docs](https://posthog.com/docs/feature-flags/local-evaluation#dynamic-cohort-restrictions) for the full list.
295
-
296
- Local eval doesn't fire `$feature_flag_called` events. PostHog Experiments counts exposures off these — `posthog-node` emits them automatically on every local eval, but this component can't do the same: Convex queries are pure functions, so they can't schedule a `capture` from inside the eval path without breaking Convex's contract. If you're running an experiment against a locally-evaluated flag, fire one manually from a mutation or action:
297
-
298
- ```ts
299
- await posthog.capture(ctx, {
300
- event: "$feature_flag_called",
301
- distinctId: userId,
302
- properties: {
303
- $feature_flag: "flag-key",
304
- $feature_flag_response: value,
305
- locally_evaluated: true,
306
- },
307
- });
308
- ```
309
-
310
- There are also reasons you might *not want* local eval at all, even when it's possible:
311
-
312
- - **Low-traffic projects.** PostHog bills each `/flags/definitions` poll as 10 flag-request equivalents. For projects that evaluate fewer flags than that per polling interval, remote evaluation is cheaper.
313
- - **Need-it-now changes.** Local eval accepts up to one polling interval of staleness (default 1 minute with our cron). For flags that must flip in well under that, you want remote eval.
314
- - **No personal API key.** If you don't want to set `POSTHOG_PERSONAL_API_KEY`, the local methods aren't useful — there's nothing for them to read.
315
-
316
- For any of those, use the remote-eval methods below instead.
317
-
318
- ### Remote evaluation
319
-
320
- Sibling methods that hit PostHog's `/flags` endpoint directly. They require an **action** context (each call is a network round trip) and don't need `personalApiKey`. They handle every case local eval can't.
321
-
322
- ```ts
323
- import { posthog } from "./posthog";
324
- import { action } from "./_generated/server";
325
- import { v } from "convex/values";
326
-
327
- export const getContinuityFlag = action({
328
- args: { userId: v.string() },
329
- handler: async (ctx, args) => {
330
- const value = await posthog.evaluateFlag(ctx, {
331
- key: "my-experience-continuity-flag",
332
- distinctId: args.userId,
333
- personProperties: { plan: "pro" },
334
- });
335
- return value;
336
- },
337
- });
338
- ```
339
-
340
- Three methods:
341
-
342
- | Method | Returns |
343
- | --- | --- |
344
- | `posthog.evaluateFlag(ctx, args)` | `FeatureFlagValue \| null` |
345
- | `posthog.evaluateFlagPayload(ctx, args)` | `JsonType \| null` |
346
- | `posthog.evaluateAllFlags(ctx, args)` | `{ featureFlags, featureFlagPayloads }` |
347
-
348
- Same option shape as the local methods (`groups`, `personProperties`, `groupProperties`, `disableGeoip`, `flagKeys` on the all-flags variant). Pick local when the flag is suitable and the cost of `/flags/definitions` polling is justified; pick remote when it isn't.
349
-
350
- ## 🔄 Differences from `posthog-node`
351
-
352
- Method names and option shapes (`groups`, `personProperties`, `groupProperties`, `disableGeoip`, `flagKeys`) match `posthog-node` where they reasonably can. The differences:
353
-
354
- - **Every method takes a Convex `ctx` first.** `posthog.capture(ctx, { … })` rather than `posthog.capture({ … })`. Required by Convex's runtime.
355
- - **Flag methods and `captureException` use an args object instead of positional args.** `getFeatureFlag(ctx, { key, distinctId, … })` rather than `getFeatureFlag(key, distinctId, …)`. The event methods (`capture`, `identify`, `alias`, `groupIdentify`) already use args objects in `posthog-node`, so those match.
356
- - **No `captureImmediate` / `identifyImmediate` / `aliasImmediate` variants.** All component actions use the `Immediate` paths under the hood — Convex isolates don't have a clean lifecycle hook for batching and flushing, so the queued mode is gone.
357
- - **No `flush()` / `shutdown()`.** Same reason — there's nothing to flush.
358
- - **Local-eval methods don't auto-fall-back to remote.** `posthog-node`'s `getFeatureFlag` quietly hits `/flags` when local eval can't reach a verdict. Ours returns `undefined` (or `null` from `getFeatureFlagResult`) and you call `evaluateFlag` / `evaluateFlagPayload` / `evaluateAllFlags` explicitly for remote. Auto-fallback would force every local-eval call into an action context (since queries can't make network calls), which would defeat the reactivity win.
359
- - **Local-eval methods throw when `POSTHOG_PERSONAL_API_KEY` isn't configured.** `posthog-node` returns `undefined`; the throw here points you at the remote `evaluate*` methods so you can't get stuck wondering why your rollouts don't take effect.
360
-
361
- ## ⬆️ Migrating from v1
362
-
363
- v2 moves credentials from the client constructor onto the component itself, using [Convex 1.39's typed component env vars](https://docs.convex.dev/components/authoring#environment-variables). It also bundles the refresh cron inside the component. The result is less plumbing per call site and a setup that's safe to leave running on free-tier dev deployments.
364
-
365
- To upgrade:
366
-
367
- 1. **Bump your app's `convex` dependency** to `^1.39.0` (required for the typed component env-var API).
368
- 2. **Rename** the `POSTHOG_API_KEY` env var to `POSTHOG_PROJECT_TOKEN`. The new name is unambiguous: this is your PostHog project token (`phc_…`), distinct from `POSTHOG_PERSONAL_API_KEY` (the `phx_…` / `phs_…` key used for local flag evaluation).
369
- ```sh
370
- npx convex env set POSTHOG_PROJECT_TOKEN phc_your_project_token
371
- npx convex env unset POSTHOG_API_KEY
372
- ```
373
- `POSTHOG_PROJECT_TOKEN` is now **required at deploy time** (declared as `v.string()` on the component). In v1 the component would deploy without it set and silently no-op event sends at runtime; v2 fails fast at deploy. Make sure the env var is set before deploying.
374
- 3. **Declare the env vars on your app and forward them to the component** in `convex/convex.config.ts`:
375
- ```ts
376
- const app = defineApp({
377
- env: {
378
- POSTHOG_PROJECT_TOKEN: v.string(),
379
- POSTHOG_HOST: v.optional(v.string()),
380
- POSTHOG_PERSONAL_API_KEY: v.optional(v.string()),
381
- },
382
- });
383
- app.use(posthog, {
384
- env: {
385
- POSTHOG_PROJECT_TOKEN: app.env.POSTHOG_PROJECT_TOKEN,
386
- POSTHOG_HOST: app.env.POSTHOG_HOST,
387
- POSTHOG_PERSONAL_API_KEY: app.env.POSTHOG_PERSONAL_API_KEY,
388
- },
389
- });
390
- ```
391
- 4. **Drop the credential options** from `new PostHog(...)`:
392
- ```diff
393
- - export const posthog = new PostHog(components.posthog, {
394
- - apiKey: process.env.POSTHOG_API_KEY,
395
- - personalApiKey: process.env.POSTHOG_PERSONAL_API_KEY,
396
- - host: process.env.POSTHOG_HOST,
397
- - });
398
- + export const posthog = new PostHog(components.posthog);
399
- ```
400
- 5. **Delete your `convex/crons.ts`** if it only existed to refresh PostHog flag definitions — the component ships its own cron now, conditionally registered only when `POSTHOG_PERSONAL_API_KEY` is set. `posthog.refreshFlagDefinitions(ctx)` was renamed to `posthog.reloadFeatureFlags(ctx)` for parity with `posthog-node`; the cron is the primary refresh path but you can still call this manually from an action when you need an immediate refresh.
401
-
402
- Everything else — the `capture`, `identify`, `getFeatureFlag`, `evaluateFlag`, etc. APIs — is unchanged.
403
-
404
- ## 📦 Example
405
-
406
- See the [example app](../../examples/example-convex/) for a working demo.
407
-
408
- ## 🤝 Contributing
8
+ Please see the main [PostHog docs](https://posthog.com/docs).
409
9
 
410
- See [CONTRIBUTING.md](CONTRIBUTING.md) for package-specific development instructions.
10
+ SDK usage examples and code snippets live in the official documentation so they stay up to date.
411
11
 
412
- ## 📄 License
12
+ ## Documentation
413
13
 
414
- MIT
14
+ - [Convex library docs](https://posthog.com/docs/libraries/convex)
@@ -1,2 +1,2 @@
1
- export declare const version = "2.0.27";
1
+ export declare const version = "2.0.29";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,2 +1,2 @@
1
- export const version = '2.0.27';
1
+ export const version = '2.0.29';
2
2
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "url": "https://github.com/PostHog/posthog-js/issues"
12
12
  },
13
13
  "author": "PostHog Inc.",
14
- "version": "2.0.27",
14
+ "version": "2.0.29",
15
15
  "license": "MIT",
16
16
  "keywords": [
17
17
  "convex",
@@ -48,8 +48,8 @@
48
48
  "types": "./dist/client/index.d.ts",
49
49
  "module": "./dist/client/index.js",
50
50
  "dependencies": {
51
- "@posthog/core": "1.32.2",
52
- "posthog-node": "5.36.16"
51
+ "@posthog/core": "^1.35.2",
52
+ "posthog-node": "^5.38.1"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@edge-runtime/vm": "^5.0.0",
@@ -1 +1 @@
1
- export const version = '2.0.27'
1
+ export const version = '2.0.29'