Cloudflare Workers

This doc focuses on features unique to Cloudflare Workers. If you're looking for details on frameworks like React, Vue, Remix, Svelte, Hono, Astro, and more see their framework-specific docs.

Installation

To use PostHog in Cloudflare Workers, start by installing the posthog-node library:

Terminal
npm i posthog-node

Afterwards, set up your project token and host in your wrangler.jsonc (or wrangler.toml) file like this:

JSON
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-posthog-worker",
"main": "src/index.js",
"compatibility_date": "2025-06-20",
"observability": {
"enabled": true
},
"vars": {
"POSTHOG_TOKEN": "<ph_project_token>",
"POSTHOG_HOST": "https://us.i.posthog.com",
},
}

Next, set up the PostHog helper to create a PostHog client. We set flushAt to 1 and flushInterval to 0 to send captured data without batching. Batched data is sent asynchronously and Cloudflare Workers can terminate before it's sent causing data loss.

JSX
// src/posthog.js
import { PostHog } from 'posthog-node'
export function createPostHogClient(env) {
const posthog = new PostHog(env.POSTHOG_TOKEN, {
host: env.POSTHOG_HOST,
flushAt: 1, // Send events immediately in edge environment
flushInterval: 0, // Don't wait for interval
})
return posthog
}

Usage

With PostHog installed, you can add and use it in your app like this:

JSX
// src/index.js
import { createPostHogClient } from './posthog.js';
export default {
async fetch(request, env, ctx) {
const posthog = createPostHogClient(env);
const distinctId = 'ian@posthog.com' // replace with actual user ID
ctx.waitUntil(
posthog.captureImmediate({
distinctId: distinctId,
event: 'hello_world_request',
properties: {
$current_url: request.url
}
})
);
const flag = await posthog.isFeatureEnabled('test_flag', distinctId) || false
ctx.waitUntil(
posthog.shutdown()
);
return new Response('Hello World! ' + flag);
},
};

You'll notice two different usage patterns here:

  1. We use ctx.waitUntil() with captureImmediate() to capture an event. This doesn't block the response, but does ensure data is captured before the worker shuts down.
  2. We await flags which blocks the response until we get the feature flag data we need.

Error tracking

You can capture errors in Cloudflare Workers like you would in other Node applications using posthog.captureException().

For more details and a method for automatically capturing errors, see our error tracking installation docs.

Node.js compatibility

posthog-node ships a dedicated workerd export that avoids Node.js built-ins — it does not require nodejs_compat on its own. However, other dependencies in your project may need it. If you see missing-module errors at import time, add the compatibility flag to your Wrangler config:

toml
compatibility_flags = ["nodejs_compat"]

Environment variables

Workers historically had no process.env, but since April 2025 (compatibility date 2025-04-01+), process.env is populated automatically when using the nodejs_compat flag. You can also use import { env } from 'cloudflare:workers' to access bindings from anywhere, including top-level scope.

How you access environment variables depends on your framework:

FrameworkServer-side env access
React Router 7context.cloudflare.env.VAR_NAME (in loaders, actions, middleware)
SvelteKitplatform.env.VAR_NAME (in hooks and server routes)
Nuxtprocess.env works via unenv polyfill — but only inside event handlers, not at top-level. Prefer useRuntimeConfig(event)
Astro 5Astro.locals.runtime.env.VAR_NAME (in SSR pages and API routes)
Astro 6+import { env } from 'cloudflare:workers' (direct import; Astro.locals.runtime was removed)
Honoenv.VAR_NAME from the fetch handler's env parameter
Raw Workersenv.VAR_NAME from the fetch handler's env parameter

Define variables in wrangler.toml under [vars] (non-secret) or via wrangler secret put (secret).

Event flushing

Workers isolates are stateless — they may be reused across requests on the same edge location, but have no guaranteed longevity. Use ctx.waitUntil() to flush after the response is sent. The ctx / waitUntil source depends on your framework:

FrameworkwaitUntil access
React Router 7context.cloudflare.ctx.waitUntil()
SvelteKitplatform.ctx.waitUntil()
Astro 5Astro.locals.runtime.ctx.waitUntil()
Astro 6+Astro.locals.cfContext.waitUntil()
Honoc.executionCtx.waitUntil()
Raw Workersctx.waitUntil() (the ExecutionContext directly)

As a framework-agnostic alternative, you can import { waitUntil } from 'cloudflare:workers' and call it from anywhere.

We recommend creating a new PostHog client per request. Workers may reuse globals across requests on the same isolate, but the shutdown/flush lifecycle makes per-request instantiation safer.

Further reading

Community questions

Was this page useful?

Questions about this page? or post a community question.