How OpenWork Implements Analytics Key Management: A Deep Dive into PostHog Integration

OpenWork resolves its PostHog analytics key through environment variable overrides with smart fallbacks—production builds use a hard-coded default, development runs silent, and empty strings explicitly disable tracking.

OpenWork is an open-source desktop application for asynchronous work management that uses PostHog for privacy-conscious, self-hosted product analytics. This article examines how the codebase handles analytics key security, environment-based configuration, and user-controlled opt-outs according to the different-ai/openwork repository.

Analytics Key Resolution Strategy

The core logic lives in analytics-key.ts, where the resolvePosthogKey function implements a three-tier resolution strategy:

// apps/app/src/app/lib/analytics-key.ts
export const DEFAULT_POSTHOG_KEY = "phc_4YnPTlDVYPjgwKvLuNxhbHjV5kadgvd7XLzVHWnCXAI";

export function resolvePosthogKey(raw: unknown, isDev: boolean): string {
  if (typeof raw === "string") return raw.trim(); // explicit value wins; "" disables
  return isDev ? "" : DEFAULT_POSTHOG_KEY;
}

The resolution order is strict:

  • Environment override wins: If VITE_OPENWORK_POSTHOG_KEY is set, its trimmed value is used unconditionally—even an empty string
  • Production fallback: In production builds (import.meta.env.DEV === false), the hard-coded DEFAULT_POSTHOG_KEY is used
  • Development silence: In development, the key remains empty, disabling analytics by default

This design prevents accidental tracking during local development while ensuring production deployments never fail silently due to missing configuration.

Configuration Integration in the Analytics Module

The resolved key feeds into analytics.ts, which also handles host configuration:

// apps/app/src/app/lib/analytics.ts
const POSTHOG_KEY = resolvePosthogKey(
  import.meta.env.VITE_OPENWORK_POSTHOG_KEY,
  import.meta.env.DEV
);
const POSTHOG_HOST = (ENV_POSTHOG_HOST || DEFAULT_POSTHOG_HOST).replace(/\/+$/, "");

Key characteristics:

Aspect Implementation
Host override VITE_OPENWORK_POSTHOG_HOST env variable
Default host https://us.i.posthog.com
URL normalization Trailing slashes stripped with regex

User-Controlled Enable/Disable Mechanism

Analytics respect user autonomy through a stored preference. The isAnalyticsEnabled() helper reads from localStorage via the settings system:

import { isAnalyticsEnabled } from '@/app/lib/analytics';

if (isAnalyticsEnabled()) {
  captureAnalyticsEvent('feature_used', { feature: 'quick-search' });
}

The preference defaults to enabled when undefined or malformed—analytics remain active unless the user explicitly opts out through Settings → Preferences.

Key Security and Deployment Considerations

Production Key Protection

The DEFAULT_POSTHOG_KEY is embedded directly in source code rather than fetched remotely. This is intentional: PostHog project keys are write-only public tokens by design. They cannot read data or perform destructive operations, so client-side exposure carries minimal risk.

Empty String as Kill Switch

Setting VITE_OPENWORK_POSTHOG_KEY='' intentionally disables analytics in any environment. This provides an emergency override for air-gapped deployments or compliance requirements.

Testing Key Resolution Logic

The behavior is validated in analytics-key.test.ts ([^12^]). Unit tests confirm:

  • Explicit strings override all defaults
  • Empty strings disable regardless of environment
  • Production builds without overrides receive DEFAULT_POSTHOG_KEY
  • Development builds without overrides receive empty string

Distinct ID and Identity Management

Even with a valid key, analytics require stable user identification. The getAnalyticsDistinctId() function in analytics.ts lazily generates a UUID:

import { getAnalyticsDistinctId, identify } from '@/app/lib/analytics';

const anonymousId = getAnalyticsDistinctId(); // Creates and stores UUID if missing

// After sign-in, link to real user without losing history
identify(denUser.id); // Emits $identify event preserving DAU continuity

This preserves accurate daily active user metrics when anonymous users later authenticate, without ever transmitting personal data.

Event Pipeline Architecture

With key management resolved, the analytics system operates fire-and-forget:

  1. Queue: Events buffered in memory
  2. Batch: Flushed every 10 seconds or 50 items
  3. Endpoint: POST to ${POSTHOG_HOST}/batch/
  4. Failure handling: Network errors swallowed silently—UI never blocks
import { captureAnalyticsEvent, flushAnalytics } from '@/app/lib/analytics';

// Automatic batching
captureAnalyticsEvent('document_created', { template: 'brief' });

// Manual flush for critical paths (rarely needed)
await flushAnalytics();

Summary

  • resolvePosthogKey in analytics-key.ts implements environment-aware key resolution with explicit override support
  • Production deployments use embedded DEFAULT_POSTHOG_KEY unless VITE_OPENWORK_POSTHOG_KEY overrides
  • Development builds disable analytics by default (empty key) to prevent noise
  • User preferences via analyticsEnabled provide runtime opt-out independent of key configuration
  • PostHog keys are public by design—embedding them in client code is safe and standard practice
  • Identity bridging through identify() maintains metric continuity across anonymous-to-authenticated transitions

Frequently Asked Questions

How do I disable analytics completely in OpenWork?

Set VITE_OPENWORK_POSTHOG_KEY='' (empty string) in your environment variables. This overrides all defaults and disables tracking in both development and production builds. Alternatively, users can toggle off the analyticsEnabled preference in Settings → Preferences at runtime.

Is it safe that the PostHog key is visible in the source code?

Yes. PostHog project keys are public write-only tokens designed for client-side use. They can only submit events, not query data or modify configuration. The security model relies on project-level access controls in your PostHog instance, not key secrecy.

How does OpenWork handle analytics during local development?

By default, development builds receive an empty key from resolvePosthogKey, making analytics silent. Developers can override this by setting VITE_OPENWORK_POSTHOG_KEY to a test project key when validating instrumentation locally.

What happens to queued events if the user disables analytics mid-session?

Events already queued remain in memory until the next flush attempt, but subsequent captureAnalyticsEvent calls return immediately without buffering. The isAnalyticsEnabled() check gates both capture and flush operations, ensuring no new data transmits after opt-out.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →