/conventions
PostHog coding conventions for frontend and backend development
$ npx -y skills add posthog/posthog --agent claude-codeHow it fires
How this command gets triggered: by you, by Claude, or both.
- Fires itselfClaude auto-loads it when your prompt matches the work.
- You can call itInvoke it directly when you want it.
- Slash command
/conventions
Context preview
What this command does when you run it.
PostHog coding conventions for frontend and backend development
Command definition
conventions.mddescription: PostHog coding conventions for frontend and backend development
PostHog Coding Conventions
When writing code, follow these PostHog coding conventions.
> **Source of truth**: These conventions are maintained at: > > - https://posthog.com/handbook/engineering/conventions/frontend-coding > - https://posthog.com/handbook/engineering/conventions/backend-coding > > If this file gets out of sync, update it from the source.
---
Frontend Coding Conventions
Two layers: Kea -> React
Our frontend webapp is written with [Kea](https://keajs.org/) and [React](https://reactjs.org/) as two separate layers. Kea is used to organise the app's data for rendering (we call this the _data_ or _state_ layer), and React is used to render the computed state (this is the _view_ or _template_ layer).
We try to be very explicit about this separation, and avoid local React state wherever possible, with exceptions for the `lib/` folder. Having all our data in one layer makes for code that's easier to [test](https://keajs.org/docs/intro/testing), and observe. Basically, getting your [data layer](https://keajs.org/blog/data-first-frontend-revolution) right is hard enough. We aim to not make it harder by constraining your data to a DOM-style hierarchy.
Hence the explicit separation between the data and view layers.
General tips
- Think data first: get [your mental model of the data flowing through the app](https://acco.io/i-escaped-node) right, and then everything else will be simpler.
- Be practical, yet remember that you are balancing speed of delivery with ease of maintainability. If you have to choose: code should be easier to understand than it was to write.
Do-s & Don't-s
- General
- Write all new code with TypeScript and proper typing.
- Write your frontend data handling code first, and write it in a Kea `logic`.
- Don't use `useState` or `useEffect` to store local state. It's false convenience. Take the extra 3 minutes and change it to a `logic` early on in the development.
- Logics still have a tiny initialization cost. Hence this rule doesn't apply to library components in the `lib/` folder, which might be rendered hundreds of times on a page with different sets of data. Still feel free to write a logic for a complicated `lib/` component when needed.
- Use named exports (`export const DashboardMenu = () => <div />`), and avoid `default` exports.
- Naming things:
- Always look around the codebase for naming conventions, and follow the best practices of the environment (e.g. use `camelCase` variables in JS, `snake_case` in Python).
- Use clear, yet functional names (`searchResults` vs `data`).
- Logics are camelCase (`dashboardLogic`)
- React components are PascalCase (`DashboardMenu`).
- Props for both logics and components are PascalCase and end with `Props` (`DashboardLogicProps` & `DashboardMenuProps`)
- Name the `.ts` file according to its main export: `DashboardMenu.ts` or `DashboardMenu.tsx` or `dashboardLogic.ts` or `Dashboard.scss`. Pay attention to the case.
- Avoid `index.ts`, `styles.css`, and other generic names, even if this is the only file in a directory.
- Component structure & reuse
- One component per file — a file exports the component its name promises. Avoid re-export shims and barrel files: every symbol should have exactly one import path, and moving a symbol means updating its consumers, not leaving a compatibility stub.
- Reach for an existing design-system component (Lemon/quill) before hand-rolling markup — reuse is how new UI stays on-brand. When you genuinely need a custom component, build it from the system's tokens and primitives and match the surrounding scene's density; avoid the generic AI-generated look (purple gradients, glassmorphism, gradient text, icon-tile card grids, decorative motion).
- Before building new UI, read a few comparable scenes or components and model yours on the ones that follow these conventions. The codebase contains legacy that predates them — an existing violation is not license to repeat it. Conventions outrank precedent, and compliant precedent outranks invention.
- Extract a shared component once the same shape appears in several places and the call sites read as content, not markup. Keep new generics next to the feature that uses them, and promote to `lib/` only when a second feature needs them. Don't build wrappers with a single consumer, and don't add boolean variant props so one caller can switch half the component off — that's two components.
- Interactive elements are real `<button>`/`<a>` elements (`LemonButton` renders one) — never `onClick` on a `<div>`.
- Loading, empty, and error are three different screens. Never show an empty state from data that hasn't resolved yet — branch on the loading state first.
- When renaming a feature, sweep code symbols completely — but analytics-facing strings (event names, property names and values, `data-attr` values) and persisted keys are a frozen API: leave them as-is, with a comment noting they're pinned.
- Scenes
- Our app is built of _scenes_, managed through a scene router in `sceneLogic`.
- A scene is the smallest unit in the router and for code splitting. Usually we split scenes by resource type (dashboard, insight) and function (edit, index).
- Each scene (e.g. Dashboards) exports an object of type `SceneExport`, containing the scene's root `logic` and its React `component`.
- The scene's logic is automatically mounted and receives the scene's URL params as props (via `paramsToProps`).
- Use `urlToAction` and `actionToUrl` on the scene's logic to sync state with the URL. Try to only use them on the scene's logic, not in any deeper logics.
- Logics mounted by React components through the view layer unmount when the component unmounts. Use `useAttachedLogic(dataNodeLogic(propsFromComponent), mySceneLogic())` to attach a logic to the scene's logic so it persists until the scene's logic is unmounted, sur
Read more
description: PostHog coding conventions for frontend and backend development
PostHog Coding Conventions
When writing code, follow these PostHog coding conventions.
> **Source of truth**: These conventions are maintained at: > > - https://posthog.com/handbook/engineering/conventions/frontend-coding > - https://posthog.com/handbook/engineering/conventions/backend-coding > > If this file gets out of sync, update it from the source.
---
Frontend Coding Conventions
Two layers: Kea -> React
Our frontend webapp is written with [Kea](https://keajs.org/) and [React](https://reactjs.org/) as two separate layers. Kea is used to organise the app's data for rendering (we call this the _data_ or _state_ layer), and React is used to render the computed state (this is the _view_ or _template_ layer).
We try to be very explicit about this separation, and avoid local React state wherever possible, with exceptions for the `lib/` folder. Having all our data in one layer makes for code that's easier to [test](https://keajs.org/docs/intro/testing), and observe. Basically, getting your [data layer](https://keajs.org/blog/data-first-frontend-revolution) right is hard enough. We aim to not make it harder by constraining your data to a DOM-style hierarchy.
Hence the explicit separation between the data and view layers.
General tips
- Think data first: get [your mental model of the data flowing through the app](https://acco.io/i-escaped-node) right, and then everything else will be simpler.
- Be practical, yet remember that you are balancing speed of delivery with ease of maintainability. If you have to choose: code should be easier to understand than it was to write.
Do-s & Don't-s
- General
- Write all new code with TypeScript and proper typing.
- Write your frontend data handling code first, and write it in a Kea `logic`.
- Don't use `useState` or `useEffect` to store local state. It's false convenience. Take the extra 3 minutes and change it to a `logic` early on in the development.
- Logics still have a tiny initialization cost. Hence this rule doesn't apply to library components in the `lib/` folder, which might be rendered hundreds of times on a page with different sets of data. Still feel free to write a logic for a complicated `lib/` component when needed.
- Use named exports (`export const DashboardMenu = () => <div />`), and avoid `default` exports.
- Naming things:
- Always look around the codebase for naming conventions, and follow the best practices of the environment (e.g. use `camelCase` variables in JS, `snake_case` in Python).
- Use clear, yet functional names (`searchResults` vs `data`).
- Logics are camelCase (`dashboardLogic`)
- React components are PascalCase (`DashboardMenu`).
- Props for both logics and components are PascalCase and end with `Props` (`DashboardLogicProps` & `DashboardMenuProps`)
- Name the `.ts` file according to its main export: `DashboardMenu.ts` or `DashboardMenu.tsx` or `dashboardLogic.ts` or `Dashboard.scss`. Pay attention to the case.
- Avoid `index.ts`, `styles.css`, and other generic names, even if this is the only file in a directory.
- Component structure & reuse
- One component per file — a file exports the component its name promises. Avoid re-export shims and barrel files: every symbol should have exactly one import path, and moving a symbol means updating its consumers, not leaving a compatibility stub.
- Reach for an existing design-system component (Lemon/quill) before hand-rolling markup — reuse is how new UI stays on-brand. When you genuinely need a custom component, build it from the system's tokens and primitives and match the surrounding scene's density; avoid the generic AI-generated look (purple gradients, glassmorphism, gradient text, icon-tile card grids, decorative motion).
- Before building new UI, read a few comparable scenes or components and model yours on the ones that follow these conventions. The codebase contains legacy that predates them — an existing violation is not license to repeat it. Conventions outrank precedent, and compliant precedent outranks invention.
- Extract a shared component once the same shape appears in several places and the call sites read as content, not markup. Keep new generics next to the feature that uses them, and promote to `lib/` only when a second feature needs them. Don't build wrappers with a single consumer, and don't add boolean variant props so one caller can switch half the component off — that's two components.
- Interactive elements are real `<button>`/`<a>` elements (`LemonButton` renders one) — never `onClick` on a `<div>`.
- Loading, empty, and error are three different screens. Never show an empty state from data that hasn't resolved yet — branch on the loading state first.
- When renaming a feature, sweep code symbols completely — but analytics-facing strings (event names, property names and values, `data-attr` values) and persisted keys are a frozen API: leave them as-is, with a comment noting they're pinned.
- Scenes
- Our app is built of _scenes_, managed through a scene router in `sceneLogic`.
- A scene is the smallest unit in the router and for code splitting. Usually we split scenes by resource type (dashboard, insight) and function (edit, index).
- Each scene (e.g. Dashboards) exports an object of type `SceneExport`, containing the scene's root `logic` and its React `component`.
- The scene's logic is automatically mounted and receives the scene's URL params as props (via `paramsToProps`).
- Use `urlToAction` and `actionToUrl` on the scene's logic to sync state with the URL. Try to only use them on the scene's logic, not in any deeper logics.
- Logics mounted by React components through the view layer unmount when the component unmounts. Use `useAttachedLogic(dataNodeLogic(propsFromComponent), mySceneLogic())` to attach a logic to the scene's logic so it persists until the scene's logic is unmounted, sur
:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.
Repo: posthog/posthog

