adding-framework-support · v1.0 · 2026-02-04 · sha256 9e2588c278fd7466
adding-framework-support v1.0A
Immutable. This exact content is served forever at /api/v1/blob/9e2588c278fd7466.
---
name: adding-framework-support
description: Add a new framework integration to the PostHog wizard. Use when adding support for a new language or framework (e.g. Ruby on Rails, Go, Angular). Covers creating the agent config, detection logic, registry entry, and enum/label additions.
compatibility: Designed for Claude Code working on the PostHog wizard codebase.
metadata:
author: posthog
version: "1.0"
---
# Adding Framework Support
## Architecture Overview
Every framework integration is a single `FrameworkConfig` object. The wizard has no switch statements or per-framework routing — everything is data-driven through:
1. **`FrameworkConfig`** (`src/lib/framework-config.ts`) — the interface each framework implements
2. **`FRAMEWORK_REGISTRY`** (`src/lib/registry.ts`) — maps `Integration` enum values to configs
3. **`Integration` enum** (`src/lib/constants.ts`) — enum order determines detection priority and menu display order
The universal runner (`src/lib/agent-runner.ts`) handles all shared behavior: debug logging, version checking, welcome message, beta notices, AI consent, credential flow, agent execution, error handling, and outro messaging.
## Steps to Add a New Framework
### 1. Add to the Integration enum and labels
In `src/lib/constants.ts`, add the new value to `Integration`. **Enum order matters** — it controls both the detection priority (first match wins) and the display order in the CLI select menu. The display label comes from `metadata.name` in your `FrameworkConfig`.
```ts
export enum Integration {
// ... existing entries
rails = 'rails', // insert at the desired detection/display position
}
```
### 2. Create the agent config file
Create `src/<framework>/<framework>-wizard-agent.ts`. This file exports:
- A context type for framework-specific data
- A `FrameworkConfig<TContext>` object (the full integration definition)
- A thin runner function that just calls `runAgentWizard(CONFIG, options)`
Define a context type for any data gathered before the agent runs, then pass it as the generic parameter. Use `type` (not `interface`) so it satisfies the `Record<string, unknown>` constraint:
```ts
type RailsContext = {
projectType?: RailsProjectType;
gemfilePath?: string;
};
export const RAILS_AGENT_CONFIG: FrameworkConfig<RailsContext> = {
// All context-consuming callbacks (getTags, getOutroChanges, etc.)
// are now fully typed — no `any` casts needed.
};
```
Use an existing config as a template. The config has these sections:
#### `metadata`
- `name` — display name (e.g. "Ruby on Rails")
- `integration` — the enum value
- `docsUrl` — PostHog docs URL for manual setup fallback
- `unsupportedVersionDocsUrl` — optional fallback for old versions
- `beta` — set `true` to show a `[BETA]` notice before running
- `gatherContext` — optional async function to detect project-specific context (e.g. router type, project variant)
#### `detection`
- `packageName` — the package to check (e.g. `'rails'`)
- `packageDisplayName` — human-readable name for error messages
- `usesPackageJson` — set `false` for non-JS frameworks (Python, PHP, Ruby, etc.)
- `getVersion` — extract version from package.json (return `undefined` if `usesPackageJson: false`)
- `getVersionBucket` — optional function to bucket versions for analytics (e.g. `'7.x'`)
- `minimumVersion` — optional minimum version string; runner auto-checks and bails if too old
- `getInstalledVersion` — async function to get the installed version
- `detect` — async function that returns `true` if this framework is present in the project
#### `environment`
- `uploadToHosting` — whether to offer uploading env vars to hosting providers
- `getEnvVars` — returns the env var names and values for this framework
#### `analytics`
- `getTags` — returns analytics tags from gathered context
#### `prompts`
- `projectTypeDetection` — text describing how to confirm the project type
- `packageInstallation` — text describing package manager conventions
- `getAdditionalContextLines` — optional function returning extra prompt lines from context
#### `ui`
- `successMessage`, `estimatedDurationMinutes`
- `getOutroChanges` — returns "what the agent did" bullets
- `getOutroNextSteps` — returns "next steps" bullets
### 3. Register in the framework registry
In `src/lib/registry.ts`, import the config and add it:
```ts
import { NEW_AGENT_CONFIG } from '../<framework>/<framework>-wizard-agent';
export const FRAMEWORK_REGISTRY: Record<Integration, FrameworkConfig> = {
// ... existing entries
[Integration.newFramework]: NEW_AGENT_CONFIG,
};
```
### 4. Create framework utilities (if needed)
If the framework needs project type detection, version extraction, or other complex logic, create `src/<framework>/utils.ts` with the relevant functions. Keep this separate from the agent config to maintain testability.
## Detection Guidelines
- For JS/TS frameworks: check `package.json` for the framework package using `hasPackageInstalled` and `tryGetPackageJson` from `src/utils/clack-utils.ts` and `src/utils/package-json.ts`
- For Python frameworks: glob for `requirements*.txt`, `pyproject.toml`, `setup.py`, `Pipfile` and check contents
- For PHP frameworks: check `composer.json` or framework-specific files (e.g. `artisan` for Laravel)
- For Ruby frameworks: check `Gemfile` or `Gemfile.lock` for the framework gem
- Always ignore virtual environment and dependency directories in globs
## Verification
After adding a framework:
```bash
pnpm build # Must compile with no errors
pnpm test # All tests must pass
pnpm fix # No new lint errors (warnings are OK)
```
## Reference Configs
Good examples to study:
- **JS framework**: `src/nextjs/nextjs-wizard-agent.ts` — package.json detection, context gathering (router type)
- **Python framework**: `src/django/django-wizard-agent.ts` — filesystem detection, `usesPackageJson: false`
- **PHP framework**: `src/laravel/laravel-wizard-agent.ts` — composer.json detection, multiple detection strategies