conventions

conventions is a command for Claude Code from PostHog/posthog-foss. It costs 10 tokens per session (4,163 once invoked), scanned A, original, MIT.

A reference guide describing PostHog’s frontend and backend coding conventions, including how its React application and server code are organised.

In plain words
What is it for?
It is for guiding implementation decisions when writing or changing PostHog frontend and backend code.
Why use it?
It gives coding agents the project’s expected patterns so new code fits the existing codebase.

Command for Claude Code

Written for Claude Code: installed under .claude/.

Good fit It is for guiding implementation decisions when writing or changing PostHog frontend and backend code.

Compare 6 commands from other repositories ↓
Install with agentmods
npx agentmods add commands/posthog/posthog-foss/conventions
Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

Clone the repo
git clone --depth 1 https://github.com/PostHog/posthog-foss

Made for: Claude Code.

Wrote this? Show the measurements

A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.

agentmods badge for conventions

README.md
[![agentmods](https://agentmods.dev/badge/commands/posthog/posthog-foss/conventions/github.svg)](https://agentmods.dev/commands/posthog/posthog-foss/conventions)
Your own site
<a href="https://agentmods.dev/commands/posthog/posthog-foss/conventions"><img src="https://agentmods.dev/badge/commands/posthog/posthog-foss/conventions/github.svg" alt="Measured on agentmods" height="20"></a>

Or the 80×15 button, for a site that already has a row of RSS and ATOM ones. Only the verdict fits; the numbers stay here.

agentmods 80×15 button for conventions

Your own site · 80×15
<a href="https://agentmods.dev/commands/posthog/posthog-foss/conventions"><img src="https://agentmods.dev/badge/commands/posthog/posthog-foss/conventions.svg" alt="Reviewed on agentmods" width="80" height="20"></a>
Per session 10 Only the description is in the session, so the agent can decide to use it. The body loads when it is invoked.
When invoked 4,163 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 0 findings. A grade says what 26 rules found in the file — not that it is safe.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5.1 $0.00010 $0.04163
Opus 5.5 $0.00004 $0.01665
Sonnet 5.5 $0.00002 $0.00833
Haiku 4.5 $0.00001 $0.00416

Measured 9d ago against content hash b3d4c13ff387, method: parsed. Prices are Anthropic first-party input rates as of 2026-10-07, from the pricing page.

Security

Grade A, and why

conventions scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 9d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Nothing flagged

None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.

.claude/commands/conventions.md · 219 lines

How it starts

The opening of the file, as written. The whole thing — 219 lines — stays where its author put it; the contents beside it link to each section on GitHub.

PostHog Coding Conventions

When writing code, follow these PostHog coding conventions.

Source of truth: These conventions are maintained at:

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 and React 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, and observe. Basically, getting your data layer 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 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>.
    • With focusBasedKeyboardNavigation enabled, LemonMenu moves focus between its trigger and menu items with the arrow keys, including after the menu reopens. Custom triggers must forward onFocus and onKeyDown to preserve keyboard navigation and focus return.
    • 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, surviving React component remounts.
    • You can control what's shown on the tab via the breadcrumbs selector in your scene's logic. The last breadcrumb controls the title and the icon, the one before that controls the back button. If there are more breadcrumbs, they will be ignored.
  • Kea
    • It's worth repeating: think of the data flow. Then work to simplify it. Derive as much state as possible via selectors, update the source via cascading actions, and avoid complex loops where a value triggers a subscription which calls an action which changes the value which triggers the subscription, ...
    • Use subscriptions and propsChanged sparingly, only if you can't find any other way. These have a high chance of leading to messy, cyclic or slow data flows.
    • Try to write your code such that you only use urlToAction in your scene's logic (e.g. insightSceneLogic), and never deeper down in e.g. propertyFilterLogic.
    • Take the time and read through the Kea docs until you can explain how all the various operations (actions, reducers, selectors, listeners, subscriptions, props, events, hooks, etc) work behind the scenes. It's worth knowing your tools.
  • CSS
    • We use Tailwind CSS wherever possible
    • Where it's not possible
      • We use regular SCSS files for styling to keep things simple and maintainable in the long run, as opposed to supporting the CSS-in-JS flavour of the month.
      • Inside MyBlogComponent.tsx import MyBlogComponent.scss
      • Namespace all your CSS rules under globally unique classes that match the component's name and case, for example .DashboardMenu { put everything here }
      • We loosely follow BEM conventions. If an element can't be namespaced inside a container class (e.g. modals that break out of the containing DOM element), use BEM style names like .DashboardMenu__modal to keep things namespaced.
    • Keep an eye out for custom styles in SCSS files that can be easily replaced with Tailwind classes and replace them with Tailwind when you see them
  • Testing
    • Before adding a test, make sure it earns its place and sits as low on the test pyramid as it can — the value/cost rubric in Testing below is language-agnostic.
    • Write logic tests for all logic files.
    • react testing library tests are particularly useful for components with complex interactions or to guide future humans or agents when they're changing components without full context of the uses and edge cases
    • Add all new presentational elements and scenes to our storybook. Run pnpm storybook locally.
    • In web Storybook play functions, import within and waitFor from @testing-library/dom. The preview configures their async timeout; storybook/test uses a separate default.
    • A story must render the same picture on every run. Read Deterministic stories when a story flakes or before you write one that loads data, measures itself, or uses timers.

Read the full file on GitHub · 219 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 9d ago Changed · +1 lines b3d4c13ff387
  2. 11d ago Changed · +1 lines c323d46b79fe
  3. 15d ago Changed · +1 lines c90b29db11ae
  4. 1mo ago Changed 2a3bed570f81
  5. 1mo ago First seen · 216 lines · 10 tokens per session scan A 8723434474b5

Subscribe to this mod's changes

conventions is a command published in the GitHub repository PostHog/posthog-foss (723 stars, last pushed today), licensed MIT. It adds 10 tokens to every session and 4,163 once invoked, about $0.0000 per session on Opus 5.5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-09-03.