High-level structure of the PostHog monorepo. Some directories are aspirational (e.g., platform/ doesn't exist yet - shared code currently lives in common/).
posthog/ # Legacy monolith code
api/ # DRF views, serializers
models/ # Django models
queries/ # HogQL query runners
...
ee/ # Enterprise features (being migrated to products/ and posthog/)
products/ # Product-specific apps (see products/README.md for layout)
<product>/
backend/ # Django app (models, logic, api/, presentation/, tasks/, tests/)
frontend/ # React (scenes, components, logics)
manifest.tsx # Routes, scenes, URLs
package.json
services/ # Independent backend services
llm-gateway/ # LLM proxy service
mcp/ # Model Context Protocol service
oauth-proxy/ # OAuth proxy (Cloudflare Worker)
stripe-app/ # Stripe integration app
common/ # Shared code (transitional - prefer other locations for new code)
hogli/ # Developer CLI tooling
hogql_parser/ # HogQL parser
tools/ # Developer/CI tooling, not imported by runtime code
devenv/ # Developer environment config (intent map, process model)
platform/ # Shared platform code (aspirational - not yet created)
integrations/ # External adapters
vercel/
auth/ # Token utils
http/ # Shared HTTP clients
storage/ # S3/GCS clients
queue/ # Message queue helpers
db/ # Shared DB utilities
observability/ # Logging, tracing, metrics
User-facing features with their own backend (Django app) and frontend (React). Examples: Feature Flags, Experiments, Session Replay.
- Vertical slices: each product owns its models, logic, API, and UI
- Isolated: products don't import each other's internals
- Turbo for selective testing, tach for import boundaries
See products/README.md for how to create products. For new isolated products, see products/architecture.md for design principles (DTOs, facades, isolation rules).
- are their own deployment
- have their own domain possibly
- have logic that doesn’t belong to any specific product
- aren’t shared infrastructure
- aren’t cross-cutting glue
- aren’t frontend-facing “products”
These are not glue, because glue adapts other systems.
They are not products, because no one interacts with them as a user-facing feature.
They are not platform, because they own domain logic, not shared tooling.
Cross-cutting glue and infrastructure: external adapters (Vercel), clients, shared libs. Must not import products/services.
Why platform must not call product code:
- If platform imports and calls product code, platform becomes a hidden orchestrator
- it must know which products exist
- it must route events to product logic
- it accumulates product-specific conditionals
- dependency direction flips (platform → products)
- cycles become likely over time
That destroys the "platform is foundational" property and makes boundaries brittle.
Transitional bucket for shared code that exists today: hogli (developer CLI), hogql_parser, and other cross-cutting utilities. New code should prefer platform/, tools/, products/, or services/ — common/ should not become the default dumping ground. Items here will migrate to more specific locations over time.
Developer tooling: CLIs, linters, formatters, code generators, scaffolding scripts, CI automation. Not imported by runtime code — build-time, CI, or developer-workflow artifacts only.
Configuration for the local developer environment. The devenv/ directory holds the intent/capability model that drives hogli dev:setup — mapping developer intents (e.g., "I'm working on error tracking") to capabilities (event_ingestion, replay_storage, etc.) and the processes that provide them. Process definitions live in bin/mprocs.yaml.