What is a Product?
A product is a user-facing feature area like Feature Flags, Experiments, or Session Replay. Each product:- Owns its models, logic, API, and UI
- Is isolated from other products
- Has clear boundaries enforced by tooling
- Can be tested independently with Turborepo
Product Structure
Each product follows this standardized layout:Creating a New Product
Use thehogli CLI to scaffold a new product:
1
Bootstrap the product
apps.py, package.json, and all directories.2
Register the Django app
Add to
posthog/settings/web.py:3
Update tach.toml
Add import boundary configuration:
4
Verify structure
Backend Conventions
Django App Registration
Each product backend is a real Django app:The
label must be the product name only, not the full path. This keeps migrations and app labels stable.Import Patterns
Always use the real Python path:Isolation with Facades
Products communicate through facades - a thin, stable public API.Contracts (Frozen Dataclasses)
Define the product’s interface using frozen dataclasses:- No Django imports
- Immutable (
frozen=True) - Small and hashable
- No business logic
Facade API
Expose product functionality through a facade:Using Facades
Other products import only the facade:Why explicit mappers?Mapper functions (
_to_artifact) provide an explicit boundary where internal models become external contracts. This allows:- Controlled exposure of fields
- Field transformations and computed values
- Protection from internal model changes
Business Logic Layer
Business logic lives inbackend/logic.py:
- Validation rules
- Business invariants
- Cross-field validations
- Idempotency checks
- Side effects
Presentation Layer
The presentation layer handles HTTP concerns:- Validate incoming JSON
- Convert JSON → frozen dataclasses
- Call facade methods
- Convert frozen dataclasses → JSON
- No business logic
Frontend Organization
Product frontends use React with Kea for state management:Selective Testing
Products use Turborepo for selective test execution:- Turbo tracks dependencies via
package.json - Only runs tests for changed products
- Contracts (frozen dataclasses) define stability
- When contracts don’t change, downstream tests are skipped
See the Products Architecture document for the full technical rationale behind DTOs, facades, and isolated testing.
Isolation Rules
Forbidden
- ❌ Importing another product’s
models.py - ❌ Importing another product’s
logic.py - ❌ Importing views/serializers from another product
- ❌ Returning ORM objects from facades
Allowed
- ✅ Importing another product’s
backend.facade - ✅ Using frozen dataclasses from contracts
- ✅ Calling business logic within the same product
- ✅ Presentation calling its own facade
Next Steps
Data Model
Explore PostHog’s data structures
Contributing
Learn how to contribute code