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_KEYis set, its trimmed value is used unconditionally—even an empty string - Production fallback: In production builds (
import.meta.env.DEV === false), the hard-codedDEFAULT_POSTHOG_KEYis 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:
- Queue: Events buffered in memory
- Batch: Flushed every 10 seconds or 50 items
- Endpoint:
POSTto${POSTHOG_HOST}/batch/ - 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
resolvePosthogKeyinanalytics-key.tsimplements environment-aware key resolution with explicit override support- Production deployments use embedded
DEFAULT_POSTHOG_KEYunlessVITE_OPENWORK_POSTHOG_KEYoverrides - Development builds disable analytics by default (empty key) to prevent noise
- User preferences via
analyticsEnabledprovide 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →