
Instrument Feature Flags
- 271 installs
- 70 repo stars
- Updated August 4, 2026
- posthog/ai-plugin
instrument-feature-flags: A skill for development.
About
instrument-feature-flags: A skill for development. This provides functionality for development workflows.
- instrument-feature-flags
Instrument Feature Flags by the numbers
- 271 all-time installs (skills.sh)
- +19 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,436 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/posthog/ai-plugin --skill instrument-feature-flagsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 271 |
|---|---|
| repo stars | ★ 70 |
| Last updated | August 4, 2026 |
| Repository | posthog/ai-plugin ↗ |
How do I use instrument-feature-flags for development tasks?
Use instrument-feature-flags for development tasks
Who is it for?
Best when you're working on backend & apis and need structured help with instrument feature flags.
Skip if: Teams with no backend & apis needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to use instrument-feature-flags for development tasks, or when instrument-feature-flags: a skill for development.
What you get
Structured output aligned to instrument-feature-flags: instrument-feature-flags.
Files
Add PostHog feature flags
Use this skill to add PostHog feature flags that gate new or changed functionality. Use it after implementing features or reviewing PRs to ensure safe rollouts with feature flag controls. If PostHog is not yet installed, this skill also covers initial SDK setup. Supports any platform or language.
Supported platforms: React, Next.js, React Native, Web (JavaScript), Node.js, Python, PHP, Ruby, Go, Java, Rust, .NET, Elixir, Android, iOS, Flutter, and the REST API.
Instructions
Follow these steps IN ORDER:
STEP 1: Analyze the codebase and detect the platform. - Look for dependency files (package.json, pubspec.yaml, Podfile, Package.swift, requirements.txt, go.mod, Gemfile, composer.json, mix.exs, etc.) to determine the language and framework. - Look for lockfiles (pnpm-lock.yaml, package-lock.json, yarn.lock, bun.lockb, go.sum, pubspec.lock, Podfile.lock, Package.resolved, mix.lock) to determine the package manager.
- Check for existing PostHog setup (SDK initialization, env vars, etc.). If PostHog is already installed and initialized, skip to STEP 3.
STEP 2: Research instrumentation. (Skip if PostHog is already set up.) 2.1. Find the reference file below that matches the detected platform — it is the source of truth for SDK initialization, flag evaluation methods, and framework-specific patterns. Read it now. 2.2. If no reference matches, fall back to your general knowledge and web search. Use posthog.com/docs as the primary search source.
STEP 3: Create or find the feature flag.
- Check if a PostHog MCP server is connected. If available, use its tools to search for an existing feature flag the user wants to instrument, or create a new one.
- If no MCP server is available, instruct the user to create the flag in the PostHog dashboard.
STEP 4: Plan release conditions.
- Determine the rollout strategy (percentage rollout, user targeting, group targeting, etc.).
- Plan how the feature flag will gate the new functionality in code.
STEP 5: Instrument the feature.
- Add the feature flag code following the platform-specific reference patterns.
- Use server-side evaluation when possible to avoid UI flicker.
- Do not alter the fundamental architecture of existing files. Make additions minimal and targeted.
- You must read a file immediately before attempting to write it.
STEP 6: Set up environment variables.
- Check if the project already has PostHog environment variables configured (e.g. in
.env,.env.local, or framework-specific env files). If valid values already exist, skip this step. - If the PostHog API key is missing, use the PostHog MCP server's
projects-gettool to retrieve the project'sapi_token. If multiple projects are returned, ask the user which project to use. If the MCP server is not connected or not authenticated, ask the user for their PostHog project API key instead. - For the PostHog host URL, use
https://us.i.posthog.comfor US Cloud orhttps://eu.i.posthog.comfor EU Cloud. - Write these values to the appropriate env file using the framework's naming convention.
- Reference these environment variables in code instead of hardcoding them.
Reference files
references/react.md- React feature flags installation - docsreferences/react-native.md- React native feature flags installation - docsreferences/web.md- Web feature flags installation - docsreferences/nodejs.md- Node.js feature flags installation - docsreferences/python.md- Python feature flags installation - docsreferences/django.md- Django - docsreferences/flask.md- Flask - docsreferences/php.md- Php feature flags installation - docsreferences/laravel.md- Laravel - docsreferences/ruby.md- Ruby feature flags installation - docsreferences/ruby-on-rails.md- Ruby on rails - docsreferences/go.md- Go feature flags installation - docsreferences/java.md- Java feature flags installation - docsreferences/rust.md- Rust feature flags installation - docsreferences/dotnet.md- .net feature flags installation - docsreferences/dotnet.md- .net - docsreferences/elixir.md- Elixir feature flags installation - docsreferences/android.md- Android feature flags installation - docsreferences/ios.md- Ios feature flags installation - docsreferences/usage.md- Ios SDK usage - docsreferences/flutter.md- Flutter feature flags installation - docsreferences/api.md- API feature flags installation - docsreferences/next-js.md- Next.js - docsreferences/adding-feature-flag-code.md- Adding feature flag code - docsreferences/best-practices.md- Best practices for production-ready flags - docs
Each platform reference contains SDK-specific installation, flag evaluation, and code examples. Find the one matching the user's stack. If unlisted, use the API reference as a fallback.
Key principles
- Environment variables: Always use environment variables for PostHog keys. Never hardcode them.
- Minimal changes: Add feature flag code alongside existing logic. Don't replace or restructure existing code.
- Boolean flags first: Default to boolean flag checks unless the user specifically asks for multivariate flags.
- Server-side when possible: Prefer server-side flag evaluation to avoid UI flicker.
Android Feature Flags installation - Docs
1. 1
Install the dependency
Required
Add the PostHog Android SDK to your build.gradle dependencies:
build.gradle
PostHog AI
dependencies {
implementation("com.posthog:posthog-android:3.+")
}2. 2
Configure PostHog
Required
Initialize PostHog in your Application class:
SampleApp.kt
PostHog AI
class SampleApp : Application() {
companion object {
const val POSTHOG_PROJECT_TOKEN = "<ph_project_token>"
const val POSTHOG_HOST = "https://us.i.posthog.com"
}
override fun onCreate() {
super.onCreate()
// Create a PostHog Config with the given project token and host
val config = PostHogAndroidConfig(
apiKey = POSTHOG_PROJECT_TOKEN,
host = POSTHOG_HOST
)
// Setup PostHog with the given Context and Config
PostHogAndroid.setup(this, config)
}
}3. 3
Send events
Recommended
Once installed, PostHog will automatically start capturing events. You can also manually send events to test your integration:
Kotlin
PostHog AI
import com.posthog.PostHog
PostHog.capture(
event = "button_clicked",
properties = mapOf(
"button_name" to "signup"
)
)4. 4
Evaluate boolean feature flags
Required
Check if a feature flag is enabled:
Kotlin
PostHog AI
val isMyFlagEnabled = PostHog.isFeatureEnabled("flag-key")
if (isMyFlagEnabled) {
// Do something differently for this user
// Optional: fetch the payload
val matchedFlagPayload = PostHog.getFeatureFlagResult("flag-key")?.payload
}5. 5
Evaluate multivariate feature flags
Optional
For multivariate flags, check which variant the user has been assigned:
Kotlin
PostHog AI
val enabledVariant = PostHog.getFeatureFlag("flag-key")
if (enabledVariant == "variant-key") { // replace 'variant-key' with the key of your variant
// Do something differently for this user
// Optional: fetch the payload
val matchedFlagPayload = PostHog.getFeatureFlagResult("flag-key")?.payload
}6. 6
Running experiments
Optional
Experiments run on top of our feature flags. Once you've implemented the flag in your code, you run an experiment by creating a new experiment in the PostHog dashboard.
7. 7
Next steps
Recommended
Now that you're evaluating flags, continue with the resources below to learn what else Feature Flags enables within the PostHog platform.
| Resource | Description |
|---|---|
| Creating a feature flag | How to create a feature flag in PostHog |
| Adding feature flag code | How to check flags in your code for all platforms |
| Framework-specific guides | Setup guides for React Native, Next.js, Flutter, and other frameworks |
| How to do a phased rollout | Gradually roll out features to minimize risk |
| More tutorials | Other real-world examples and use cases |
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
API Feature Flags installation - Docs
1. 1
Evaluate the feature flag value using flags
Required
flags is the endpoint used to determine if a given flag is enabled for a certain user or not.
PostHog AI
Basic request (flags only)
curl -v -L --header "Content-Type: application/json" -d '{
"token": "<ph_project_token>",
"distinct_id": "distinct_id_of_your_user",
"groups" : {
"group_type": "group_id"
}
}' "https://us.i.posthog.com/flags?v=2"Python
import requests
import json
url = "https://us.i.posthog.com/flags?v=2"
headers = {
"Content-Type": "application/json"
}
payload = {
"token": "<ph_project_token>",
"distinct_id": "user distinct id",
"groups": {
"group_type": "group_id"
}
}
response = requests.post(url, headers=headers, data=json.dumps(payload))
print(response.json())Node.js
const response = await fetch("https://us.i.posthog.com/flags?v=2", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
token: "<ph_project_token>",
distinct_id: "user distinct id",
groups: {
group_type: "group_id",
},
}),
});
const data = await response.json();
console.log(data);Note: The groups key is only required for group-based feature flags. If you use it, replace group_type and group_id with the values for your group such as company: "Twitter".
2. 2
Include feature flag information when capturing events
Required
If you want to use your feature flag to breakdown or filter events in your insights, you'll need to include feature flag information in those events. This ensures that the feature flag value is attributed correctly to the event.
Note: This step is only required for events captured using our server-side SDKs or API.
PostHog AI
Terminal
curl -v -L --header "Content-Type: application/json" -d '{
"token": "<ph_project_token>",
"event": "your_event_name",
"distinct_id": "distinct_id_of_your_user",
"properties": {
"$feature/feature-flag-key": "variant-key"
}
}' https://us.i.posthog.com/i/v0/e/Python
import requests
import json
url = "https://us.i.posthog.com/i/v0/e/"
headers = {
"Content-Type": "application/json"
}
payload = {
"token": "<ph_project_token>",
"event": "your_event_name",
"distinct_id": "distinct_id_of_your_user",
"properties": {
"$feature/feature-flag-key": "variant-key"
}
}
response = requests.post(url, headers=headers, data=json.dumps(payload))
print(response)3. 3
Send a $feature\_flag\_called event
Optional
To track usage of your feature flag and view related analytics in PostHog, submit the $feature_flag_called event whenever you check a feature flag value in your code.
You need to include two properties with this event:
1. $feature_flag_response: This is the name of the variant the user has been assigned to e.g., "control" or "test" 2. $feature_flag: This is the key of the feature flag in your experiment.
PostHog AI
Terminal
curl -v -L --header "Content-Type: application/json" -d '{
"token": "<ph_project_token>",
"event": "$feature_flag_called",
"distinct_id": "distinct_id_of_your_user",
"properties": {
"$feature_flag": "feature-flag-key",
"$feature_flag_response": "variant-name"
}
}' https://us.i.posthog.com/i/v0/e/Python
import requests
import json
url = "https://us.i.posthog.com/i/v0/e/"
headers = {
"Content-Type": "application/json"
}
payload = {
"token": "<ph_project_token>",
"event": "$feature_flag_called",
"distinct_id": "distinct_id_of_your_user",
"properties": {
"$feature_flag": "feature-flag-key",
"$feature_flag_response": "variant-name"
}
}
response = requests.post(url, headers=headers, data=json.dumps(payload))
print(response)4. 4
Running experiments
Optional
Experiments run on top of our feature flags. Once you've implemented the flag in your code, you run an experiment by creating a new experiment in the PostHog dashboard.
5. 5
Next steps
Recommended
Now that you're evaluating flags, continue with the resources below to learn what else Feature Flags enables within the PostHog platform.
| Resource | Description |
|---|---|
| Creating a feature flag | How to create a feature flag in PostHog |
| Adding feature flag code | How to check flags in your code for all platforms |
| Framework-specific guides | Setup guides for React Native, Next.js, Flutter, and other frameworks |
| How to do a phased rollout | Gradually roll out features to minimize risk |
| More tutorials | Other real-world examples and use cases |
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
Best practices for production-ready flags - Docs
Checklist
- Call `identify()` before evaluating flags – the hash uses the wrong ID otherwise. This is the most common input problem.
- Evaluate flags server-side with local evaluation – explicit inputs, your data right there, no workarounds.
- Bootstrap client-side flags – client-side evaluation is async. Bootstrap to eliminate the gap.
- Handle `undefined` explicitly – it means "not evaluated yet," not
false. - Evaluate once, record the result – a flag is a one-time signal. Re-evaluate only on meaningful state changes.
- Evaluate where the data lives – if the data is on your server, evaluate there.
- Choose evaluation context deliberately – "server and client" is the default for compatibility, not because it's the right choice for your flag.
- Clean up flags that have done their job – a flag at 100% is done. Remove it or archive it.
- Disable client-side evaluation for server-side flags – don't let the client SDK re-evaluate what your server already decided.
- Use a reverse proxy – prevent ad blockers from disabling your flags.
- Call your flag in as few places as possible – wrap in a single function if used in multiple places.
- Name flags clearly – descriptive names, types, positive language.
- Roll out progressively – start small, monitor, then increase.
The mental model: Flags are pure functions – same flag key + same distinct ID = same result. Always. Unexpected results are almost always input problems – if the result changed, an input changed.
---
Flags are pure functions
A flag hashes two things – the flag key and the distinct ID – and returns a deterministic result. Same inputs, same output. Every time.
PostHog AI
hash("my-experiment", "user-123") → 0.31 → always 0.31On top of that, PostHog layers property targeting (does this user match?), rollout percentage (is their position below the threshold?), and variant assignment. But the foundation is the hash: same flag key + same distinct ID = same result.
Technically
"Pure function" means deterministic given a stable flag definition. The definition (rollout %, targeting rules, variants) is external state. Given the same definition, evaluation is fully deterministic on flag_key + distinct_id. Some features like experience continuity add persistence layers that introduce side effects on the server, but from your perspective as the caller, the model holds: same inputs, same output.
How the hash works
PostHog uses SHA-1:
PostHog AI
hash_key = "{flag_key}.{distinct_id}"
position = parseInt(sha1(hash_key).slice(0, 15), 16) / LONG_SCALE → float in [0, 1]
in_rollout = position <= rollout_percentage / 100For variants, a second hash with salt "variant" maps to variant ranges independently. The flag key is included so the same user gets independent assignments across different flags.
If the flag has property targeting, PostHog first checks whether the person matches the conditions. If they don't match, the hash never runs – the flag returns false.
Unexpected results are almost always input problems
If you evaluate the same flag with the same distinct ID a million times, you will get the same result a million times. It's how the math works. The hash is deterministic. It doesn't drift, it doesn't have off days, and it doesn't return different values on Tuesdays.
So when a flag returns something you didn't expect, the flag is fine, the problem is in the inputs passed to the flag. Something about the identity, the properties, or the flag definition wasn't what you assumed. Find what changed, and you've found the problem.
If you keep running into flag issues and they're not incidents, the conversation isn't about PostHog's flag behavior – it's about how your application coordinates the data that flags depend on. That's an engineering conversation about identity flows, property syncing, and evaluation architecture. No single config tweak fixes it.
We're here to help with that – this guide, PostHog AI, and professional services all exist for exactly this. But the starting point is always the same: look at the inputs.
When something goes wrong, in order of likelihood:
1. Input problems (most common). Wrong distinct ID, missing properties, changed flag definition. PostHog gives you tools to get the coordination right – bootstrapping, property overrides, server-side evaluation. 2. Output problems. The flag returned the right value but your code misread it – undefined treated as false, no handling for the loading gap, evaluating repeatedly instead of recording the result. 3. Actual incidents. Check status.posthog.com. If nothing there, it's #1 or #2. And even here: with server-side local evaluation, the SDK evaluates against cached flag definitions locally. PostHog being unreachable doesn't affect flags that are already cached. Add per-flag safe defaults and even a cold start during an outage returns usable values. An incident only breaks your flags if your implementation depends on PostHog being available at request time – which is itself an implementation gap you can close.
Resolve identity before evaluating flags
Identity is the most common input problem. The hash takes two inputs: the flag key (stable) and the distinct ID (your responsibility). If the distinct ID is wrong at the moment of evaluation, the hash produces a valid but incorrect result. The flag is working perfectly – it just answered a question about the wrong person.
If you call identify() after a flag has already been evaluated, the flag likely used the anonymous ID. The hash produced one result. After identify(), the distinct ID changes, the hash changes, and the next evaluation returns a different variant. You see a "flip" – but it's because the input changed.
Call `identify()` before any flag evaluation in auth flows. If you can't guarantee that timing, bootstrap with the stable ID at init so the distinct ID is correct from the first millisecond. See keeping flag evaluations stable for the full picture.
SPA-specific timing. In single-page applications, identify() and event captures often fire from different components during the same navigation in unpredictable order. The SDK updates the distinct_id synchronously when identify() runs, but if capture() was called first in the same execution frame, that event uses the anonymous ID. The fix: call identify() before the navigation that mounts post-auth components – in Vue, in beforeEach before next(); in React, before navigate(), not in a useEffect inside the target route.
Don't rely on flag persistence to fix identity gaps
If you've enabled experience continuity (flag persistence across authentication), consider what that's telling you: the distinct ID is changing during your session, and you need PostHog to paper over it.
That comes at a cost. Experience continuity couples flag evaluation with database writes – every evaluation reads and writes to the DB to persist the result. This mixes two concerns (evaluation and storage) that should be separate, and it's the source of known bugs where values can still change after identify(). It also means no support for local evaluation and slower flag responses.
The better fix is to make persistence unnecessary. Use device bucketing for single-device consistency, or design your identity flow so the distinct ID never changes. If you need experience continuity today, treat it as a migration path toward proper identity resolution, not a permanent solution. The identity gap it papers over is the root cause of the most common flag issues – closing that gap eliminates the need for persistence entirely.
Evaluation architecture
How you evaluate flags – where, when, and how often – determines the complexity of your implementation. Most workarounds exist because the evaluation happens in the wrong place or at the wrong time.
Evaluate once, not continuously
A flag is a one-time signal, not a continuous dependency. Evaluate it once, record the result, serve from that recording. Re-evaluate only when something meaningful changes.
Re-evaluating on every request creates cost, latency, and the conditions for "flipping" – you're giving the system repeated chances to return a different answer when inputs shift. That's not a bug. That's the pure function doing its job with different inputs.
- Feature rollouts – Evaluate when your user's state changes (upgrades, joins a cohort). Between triggers, your app already knows the answer.
- Experiments – One exposure per user. Evaluate once, record the variant, deliver that experience. If a user flips variants, the app re-asked a question it already had the answer to.
Evaluate where the data lives
If you target a flag on plan_type: "pro", your app originally told PostHog this person is Pro. Evaluate the flag from the same place that has that knowledge – your server. PostHog does the distribution math; your app provides the targeting data.
If you evaluate client-side instead, the SDK needs to fetch that property from PostHog's servers – a round-trip to look up what you originally sent it. Any flag check before that completes evaluates against incomplete data.
If you must evaluate client-side, use `setPersonPropertiesForFlags()` to set properties locally before evaluation. This avoids the round-trip when you already have the data in the browser.
Property targeting is fine – just understand that the further the evaluation is from the data, the more async complexity you take on.
Server-side local evaluation is the recommended default
Server-side local evaluation is where the pure function model is fully legible:
- All inputs are explicit. You pass the distinct ID and properties directly. When something's wrong, you log what you passed.
- Your data is right there. User plan, account type, permissions – it's in your database at request time. No syncing, no fetching.
- No workarounds needed. Client-side evaluation often requires
setPersonPropertiesForFlags(),onFeatureFlags(), and bootstrap to bridge the gap between where the data lives and where the flag evaluates. Server-side eliminates the gap.
Client-side evaluation is right when you need properties only available in the browser, real-time flag changes, or have no server. But you're trading explicit inputs for implicit ones, and every workaround bridges that gap.
Have the value before you need it
Client-side flag evaluation is async – the SDK needs to fetch values from PostHog. Any flag check before that completes returns undefined, not false.
[Bootstrap](/docs/feature-flags/bootstrapping.md) is the fix. Evaluate flags server-side and pass values to the client at init. The value exists before the page renders – no gap, no flicker.
If you can't bootstrap, use onFeatureFlags() to wait. This means you will need a loading state (spinner, skeleton) until flags arrive – it prevents showing the wrong variant but doesn't prevent a delay.
undefined is not "flag is off" nor false
posthog.getFeatureFlag() returns undefined before flags load. That means "not evaluated yet," not "flag is off."
JavaScript
PostHog AI
// Returns undefined before flags load – not false
if (posthog.getFeatureFlag('my-experiment') === 'test') {
// Never runs during the loading gap
}Handle it with bootstrap (preferred) or onFeatureFlags() (adds a loading state). You can check the current identity with posthog.get_distinct_id().
The "not loaded yet" return value varies across SDKs – some return undefined/nil/None, others return false or a defaultValue you provide. Don't assume that a falsy return means the flag is off. Check your SDK's documentation for the exact return type of getFeatureFlag() and isFeatureEnabled() when flags haven't loaded, and handle that state explicitly. If your goal is to programmatically check whether a flag exists at all, use the Feature Flags API to query flag definitions directly.
Flag hygiene
Flags are infrastructure. Like any infrastructure, they accumulate cost when left unattended. These are operational practices that keep your flag system clean and efficient.
Choose a flag type intentionally
Every flag in PostHog is configured as client-side, server-side, or both via evaluation contexts. New flags default to "server and client" – this exists for backwards compatibility (it's how all flags worked before we added evaluation contexts) and to avoid blocking users who haven't thought about their implementation yet. It's a safe starting point, not a recommendation.
If all your flags are set to both, that usually means the decision was never revisited after creation – and you're paying for client-side evaluation on flags that only need to exist on your server.
Pick the context based on where the flag is actually consumed. Server-side flags that drive backend logic don't need client SDKs fetching and evaluating them. Client-side flags for UI variations don't need server-side evaluation. "Both" is valid when a flag genuinely needs to be evaluated in both contexts – but it should be a deliberate choice, not the default you never changed.
Clean up flags that have done their job
A flag set to 100% of all users with no property targeting is a flag that has finished its job. It's always returning the same value – the rollout is complete, the experiment concluded, the feature is live. If your SDK still evaluates that flag, it can keep making billable /flags requests, keep appearing in SDK payloads, and add clutter to your codebase.
Remove the flag and hardcode the winning path. If you're not ready to remove it from code, at least archive it in PostHog so it stops being evaluated. Stale flags are the most common source of unnecessary flag evaluation. See cleaning up stale flags for the full workflow and cutting costs for more on reducing your bill.
An idea worth considering: design your flag code paths with an escape hatch you control outside of PostHog. For example, a "gate flag" that your server reads once every 30 seconds (not per user) – when it's true, the feature is fully rolled out and your code skips the per-user flag evaluation entirely. This means you stop making per-user /flags requests for that rollout as soon as it's complete, even before you remove the flag from code. And you can dial it back by setting the gate flag to false. This is also another application of "evaluate once, not continuously" – if you cache flag results, your per-user evaluation cost drops while you wait for the code cleanup.
Disable client-side evaluation for server-side flags
If a flag is evaluated server-side and the result is passed to your frontend through your own application logic, the client SDK doesn't need to evaluate it independently. But unless you explicitly disable the flag on the client, the SDK will still fetch and evaluate it – duplicating work your server already did.
This is the practical extension of "evaluate once, not continuously." Your server evaluates, your application propagates the result, and the client consumes it as application state rather than re-asking PostHog. Disable flags in the client SDK that your server already handles to eliminate redundant evaluation and reduce payload size.
Use a reverse proxy
Ad blockers can disable your Feature Flags, leading to users seeing the wrong version of your app or missing a rollout. Deploy a reverse proxy so requests go through your own domain. PostHog offers a free managed reverse proxy, or you can run your own.
Call your flag in as few places as possible
The more locations a flag appears in your code, the more likely it is to cause problems – a developer removes it in one place but forgets another. If you use a flag in multiple places, wrap it in a single function:
JavaScript
PostHog AI
function useBetaFeature() {
return posthog.isFeatureEnabled('beta-feature')
}Name flags clearly
Good naming makes flags easier to understand and maintain:
- Use descriptive names.
is_v2_billing_dashboard_enabledis clearer thanis_dashboard_enabled. - Use name types. Suffix with the purpose:
new-billing-experiment,new-billing-release. - Reflect the return type.
is_premium_userfor a boolean,selected_themefor a string. - Use positive language for booleans.
is_premium_userinstead ofis_not_premium_user– avoids double negatives.
Roll out progressively
Start at 5-10% of users, monitor metrics, then gradually increase. This is a phased rollout. At PostHog, we typically roll out to the developer first, then the internal team, then beta users, then everyone.
Use dependencies for complex rollouts
Feature flag dependencies let one flag's activation depend on another flag's state – useful for enabling complex features only after foundational components are active, or running Experiments only on users with specific features enabled. Keep dependency chains simple and avoid circular dependencies.
Be careful with "Latest" person properties
PostHog automatically creates person properties like "Latest Current URL" and "Latest Referring Domain" — these are derived from the corresponding event properties (like $current_url) and update every time a new event comes in. If you target a flag on one of these, the flag value can change with every new event. If you need to target based on a value like this, capture it once as a stable person property (e.g., first_landing_page via $set_once) and target that instead.
Reducing your bill
Stale flags are the most common source of unnecessary cost. Beyond cleaning up flags, see our dedicated guide to cutting costs for estimating and reducing your feature flag bill.
Further reading
- Identity resolution – how PostHog resolves who a user is
- Keeping flag evaluations stable – preventing the hash input from changing across auth transitions
- Local evaluation – server-side evaluation for explicit input control
- Bootstrapping – having flag values before the page renders
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
Django - Docs
PostHog makes it easy to get data about traffic and usage of your Django app. Integrating PostHog enables analytics, custom events capture, feature flags, error tracking, and more.
This guide walks you through integrating PostHog into your Django app using the Python SDK.
Beta: integration via LLM
Install PostHog for Django in seconds with our wizard by running this prompt with LLM coding agents like Cursor and Bolt, or by running it in your terminal.
npx @posthog/wizard@latest
Or, to integrate manually, continue with the rest of this guide.
Installation
To start, run pip install posthog to install PostHog’s Python SDK.
Note: Version 7.x of the PostHog Python SDK requires Python 3.10 or higher.Then, configure PostHog in your app config so it's initialized when Django starts:
your\_app/apps.py
PostHog AI
from django.apps import AppConfig
import posthog
class YourAppConfig(AppConfig):
name = 'your_app_name'
def ready(self):
posthog.api_key = '<ph_project_token>'
posthog.host = 'https://us.i.posthog.com'Next, if you haven't done so already, add your AppConfig to INSTALLED_APPS in settings.py:
settings.py
PostHog AI
INSTALLED_APPS = [
# ... other apps
'your_app_name.apps.YourAppConfig',
]You can find your project token and instance address in your project settings.
To capture events from any file, import posthog and call the method you need. For example:
Python
PostHog AI
import posthog
from posthog import identify_context
def some_request(request):
with posthog.new_context():
# Django includes request.user for anonymous visitors too. Only identify
# the context when the visitor is logged in.
if request.user.is_authenticated:
identify_context(str(request.user.pk))
posthog.capture('event_name')Events captured without a context or explicit distinct_id are sent as anonymous events with an auto-generated distinct_id. See the Python SDK docs for more details.
Identifying users
Identifying users is required. Backend events need adistinct_idthat matches the ID your frontend uses when callingposthog.identify(). Without this, backend events are orphaned — they can't be linked to frontend event captures, session replays, LLM traces, or error tracking.
>
See our guide on identifying users for how to set this up.
Django contexts middleware
The Python SDK provides a Django middleware that automatically wraps all requests with a context. This middleware extracts session and user information from each request and tags all events captured during that request with relevant metadata.
Basic setup
Add the middleware to your Django settings. If your app uses Django authentication, place it after django.contrib.auth.middleware.AuthenticationMiddleware so the middleware can use the authenticated Django user as a distinct ID fallback and capture the user's email.
Python
PostHog AI
MIDDLEWARE = [
# ... other middleware
'posthog.integrations.django.PosthogContextMiddleware',
# ... other middleware
]The middleware uses the globally configured posthog client by default, so you don't need to create or pass it a separate client instance.
The middleware automatically extracts and uses:
- Session ID from the
X-POSTHOG-SESSION-IDheader, if present - Distinct ID from the
X-POSTHOG-DISTINCT-IDheader, if present, falling back to the authenticated Django user'spk(Django's primary-key alias, which works with custom user models) - User email from the authenticated Django user's
emailasemail - Current URL as
$current_url - Request method as
$request_method - Request path as
$request_path - Forwarded IP address from
X-Forwarded-Foras$ip - User agent from
User-Agentas$user_agent
The session and distinct ID headers are sanitized before use. Empty values are ignored, control characters are removed, values are trimmed, and values are capped at 1000 characters.
All events captured during the request (including exceptions) include these properties and are associated with the extracted session and distinct ID.
If you're using PostHog JS on the frontend, configure `tracing_headers` for your Django backend hostname so browser requests include the session and distinct ID headers.
Exception capture
By default, the middleware captures exceptions and sends them to PostHog's error tracking using the globally configured posthog client. This includes Django view exceptions that Django converts into error responses.
Disable this by setting:
Python
PostHog AI
# settings.py
POSTHOG_MW_CAPTURE_EXCEPTIONS = FalseAdding custom tags
Use POSTHOG_MW_EXTRA_TAGS to add custom properties to all requests:
Python
PostHog AI
# settings.py
def add_user_tags(request):
# type: (HttpRequest) -> Dict[str, Any]
tags = {}
if hasattr(request, 'user') and request.user.is_authenticated:
# Use pk instead of id so this works with custom User primary keys.
tags['user_id'] = str(request.user.pk)
tags['email'] = request.user.email
return tags
POSTHOG_MW_EXTRA_TAGS = add_user_tagsFiltering requests
Skip tracking for certain requests using POSTHOG_MW_REQUEST_FILTER:
Python
PostHog AI
# settings.py
def should_track_request(request):
# type: (HttpRequest) -> bool
# Don't track health checks or admin requests
if request.path.startswith('/health') or request.path.startswith('/admin'):
return False
return True
POSTHOG_MW_REQUEST_FILTER = should_track_requestModifying default tags
Use POSTHOG_MW_TAG_MAP to modify or remove default tags:
Python
PostHog AI
# settings.py
def customize_tags(tags):
# type: (Dict[str, Any]) -> Dict[str, Any]
# Remove URL for privacy
tags.pop('$current_url', None)
# Add custom prefix to method
if '$request_method' in tags:
tags['http_method'] = tags.pop('$request_method')
return tags
POSTHOG_MW_TAG_MAP = customize_tagsComplete configuration example
Python
PostHog AI
# settings.py
def add_request_context(request):
# type: (HttpRequest) -> Dict[str, Any]
tags = {}
if hasattr(request, 'user') and request.user.is_authenticated:
tags['user_type'] = 'authenticated'
# Use pk instead of id so this works with custom User primary keys.
tags['user_id'] = str(request.user.pk)
else:
tags['user_type'] = 'anonymous'
# Add request info
tags['user_agent'] = request.META.get('HTTP_USER_AGENT', '')
return tags
def filter_tracking(request):
# type: (HttpRequest) -> bool
# Skip internal endpoints
return not request.path.startswith(('/health', '/metrics', '/admin'))
def clean_tags(tags):
# type: (Dict[str, Any]) -> Dict[str, Any]
# Remove sensitive data
tags.pop('user_agent', None)
return tags
POSTHOG_MW_EXTRA_TAGS = add_request_context
POSTHOG_MW_REQUEST_FILTER = filter_tracking
POSTHOG_MW_TAG_MAP = clean_tags
POSTHOG_MW_CAPTURE_EXCEPTIONS = TrueAll events captured within the request context automatically include the configured tags and are associated with the session and user identified from the request headers or Django authentication.
The middleware supports both sync (WSGI) and async (ASGI) Django applications. In async mode, it uses Django's request.auser() API when available to avoid synchronous user access.
Next steps
For any technical questions for how to integrate specific PostHog features into Django (such as analytics, feature flags, A/B testing, etc.), have a look at our Python SDK docs.
Alternatively, the following tutorials can help you get started:
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
.NET - Docs
This is an optional library you can install if you're working with .NET Core. It uses an internal queue to make calls fast and non-blocking. It also batches requests and flushes asynchronously, making it perfect to use in any part of your web app or other server side application that needs performance.
Installation
The PostHog package supports any .NET platform that targets .NET Standard 2.1 or .NET 8+, including MAUI, Blazor, and console applications. The PostHog.AspNetCore package provides additional conveniences for ASP.NET Core applications such as streamlined registration, request-scoped caching, and integration with .NET Feature Management.
Note: We actively test with ASP.NET Core. Other platforms should work but haven't been specifically tested. If you encounter issues, please report them on GitHub.
Not supported: Classic UWP (requires .NET Standard 2.0 only). Microsoft has deprecated UWP in favor of the Windows App SDK. For Unity projects, see our dedicated Unity SDK.
Terminal
PostHog AI
dotnet add package PostHog.AspNetCoreIn your Program.cs (or Startup.cs for ASP.NET Core 2.x) file, add the following code:
C#
PostHog AI
using PostHog;
var builder = WebApplication.CreateBuilder(args);
// Add PostHog to the dependency injection container as a singleton.
builder.AddPostHog();Make sure to configure PostHog with your project token, instance address, and optional personal API key. For example, in appsettings.json:
JSON
PostHog AI
{
"PostHog": {
"ProjectToken": "<ph_project_token>",
"HostUrl": "https://us.i.posthog.com"
}
}Note: If the host is not specified, the default host https://us.i.posthog.com is used.Use a secrets manager to store your personal API key. For example, when developing locally you can use the UserSecrets feature of the dotnet CLI:
Terminal
PostHog AI
dotnet user-secrets init
dotnet user-secrets set "PostHog:PersonalApiKey" "phx_..."You can find your project token and instance address in the project settings page in PostHog.
Working with .NET Feature Management
PostHog.AspNetCore supports .NET Feature Management. This enables you to use the <feature /\> tag helper and the FeatureGateAttribute in your ASP.NET Core applications to gate access to certain features using PostHog feature flags.
To use feature flags with the .NET Feature Management library, you'll need to implement the IPostHogFeatureFlagContextProvider interface. The quickest way to do that is to inherit from the PostHogFeatureFlagContextProvider class and override the GetDistinctId and GetFeatureFlagOptionsAsync methods.
C#
PostHog AI
public class MyFeatureFlagContextProvider(IHttpContextAccessor httpContextAccessor)
: PostHogFeatureFlagContextProvider
{
protected override string? GetDistinctId()
=> httpContextAccessor.HttpContext?.User.Identity?.Name;
protected override ValueTask<FeatureFlagOptions> GetFeatureFlagOptionsAsync()
{
// In a real app, you might get this information from a
// database or other source for the current user.
return ValueTask.FromResult(
new FeatureFlagOptions
{
PersonProperties = new Dictionary<string, object?>
{
["email"] = "some-test@example.com"
},
OnlyEvaluateLocally = true
});
}
}Then, register your implementation in Program.cs (or Startup.cs):
C#
PostHog AI
var builder = WebApplication.CreateBuilder(args);
builder.AddPostHog(options => {
options.UseFeatureManagement<MyFeatureFlagContextProvider>();
});With this in place, you can now use feature tag helpers in your Razor views:
HTML
PostHog AI
<feature name="awesome-new-feature">
<p>This is the new feature!</p>
</feature>
<feature name="awesome-new-feature" negate="true">
<p>Sorry, no awesome new feature for you.</p>
</feature>Multivariate feature flags are also supported:
HTML
PostHog AI
<feature name="awesome-new-feature" value="variant-a">
<p>This is the new feature variant A!</p>
</feature>
<feature name="awesome-new-feature" value="variant-b">
<p>This is the new feature variant B!</p>
</feature>You can also use the FeatureGateAttribute to gate access to controllers or actions:
C#
PostHog AI
[FeatureGate("awesome-new-feature")]
public class NewFeatureController : Controller
{
public IActionResult Index()
{
return View();
}
}Using the core package without ASP.NET Core
If you're not using ASP.NET Core (for example, in a console application, MAUI app, or Blazor WebAssembly), install the PostHog package instead of PostHog.AspNetCore. This package has no ASP.NET Core dependencies and can be used in any .NET project targeting .NET Standard 2.1 or .NET 8+.
Terminal
PostHog AI
dotnet add package PostHogThe PostHogClient class must be implemented as a singleton in your project. For PostHog.AspNetCore, this is handled by the builder.AddPostHog(); method. For the PostHog package, you can do the following if you're using dependency injection:
C#
PostHog AI
builder.Services.AddPostHog();If you're not using a builder (such as in a console application), you can do the following:
C#
PostHog AI
using PostHog;
var services = new ServiceCollection();
services.AddPostHog();
var serviceProvider = services.BuildServiceProvider();
var posthog = serviceProvider.GetRequiredService<IPostHogClient>();The AddPostHog methods accept an optional Action<PostHogOptions> parameter that you can use to configure the client.
If you're not using dependency injection, you can create a static instance of the PostHogClient class and use that everywhere in your project:
C#
PostHog AI
using PostHog;
public static readonly PostHogClient PostHog = new(new PostHogOptions {
ProjectToken = "<ph_project_token>",
HostUrl = new Uri("https://us.i.posthog.com"),
PersonalApiKey = Environment.GetEnvironmentVariable(
"PostHog__PersonalApiKey")
});Debug mode
If you're not seeing the expected events being captured, the feature flags being evaluated, or the surveys being shown, you can enable debug mode to see what's happening.
To see detailed logging, set the log level to Debug or Trace in appsettings.json:
JSON
PostHog AI
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"PostHog": "Trace"
}
},
...
}Identifying users
Identifying users is required. Backend events need adistinct_idthat matches the ID your frontend uses when callingposthog.identify(). Without this, backend events are orphaned — they can't be linked to frontend event captures, session replays, LLM traces, or error tracking.
>
See our guide on identifying users for how to set this up.
Capturing events
You can send custom events using capture:
C#
PostHog AI
posthog.Capture("distinct_id_of_the_user", "user_signed_up");Tip: We recommend using a[object] [verb]format for your event names, where[object]is the entity that the behavior relates to, and[verb]is the behavior itself. For example,project created,user signed up, orinvite sent.
Setting event properties
Optionally, you can include additional information with the event by including a properties object:
C#
PostHog AI
posthog.Capture(
"distinct_id_of_the_user",
"user_signed_up",
properties: new() {
["login_type"] = "email",
["is_free_trial"] = "true"
}
);Sending page views
If you're aiming for a backend-only implementation of PostHog and won't be capturing events from your frontend, you can send $pageview events from your backend like so:
C#
PostHog AI
using PostHog;
using Microsoft.AspNetCore.Http.Extensions;
posthog.CapturePageView(
"distinct_id_of_the_user",
HttpContext.Request.GetDisplayUrl());Request context
For ASP.NET Core apps using PostHog.AspNetCore, add request context middleware before routes that call PostHog. This reads incoming PostHog tracing headers and attaches request metadata to captures, exceptions, and feature flag evaluation inside the request.
Program.cs
PostHog AI
using PostHog;
using PostHog.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.AddPostHog();
var app = builder.Build();
app.UsePostHogRequestContext();If you're using PostHog JS on the frontend, configure `tracing_headers` for your ASP.NET Core backend hostname so browser requests include the session and distinct ID headers.
The middleware reads X-PostHog-Distinct-Id and X-PostHog-Session-Id as request-scoped analytics context. It also adds request metadata such as $current_url, $request_method, $request_path, $user_agent, and $ip. Explicit distinct IDs and event properties always override request context.
Tracing headers are client-controlled analytics context, not authentication or authorization. For security-sensitive server-side decisions, pass an authenticated distinct ID explicitly. You can ignore tracing headers while still collecting request metadata:
C#
PostHog AI
app.UsePostHogRequestContext(options =>
{
options.UseTracingHeaders = false;
});Request-context overloads like posthog.Capture("checkout started") and posthog.EvaluateFlagsAsync() use the current request distinct ID when one is available.
Error tracking
You can manually capture exceptions using CaptureException. This sends a $exception event with stack frames, inner exceptions, aggregate exceptions, source context when available, and .NET runtime metadata.
File names, line numbers, and source context depend on debug information already available from the captured .NET stack trace. PostHog doesn't support uploading .NET PDB files yet, so production builds without runtime-accessible debug information may show less detailed stack frames.
C#
PostHog AI
try
{
ProcessOrder(orderId);
}
catch (Exception exception)
{
posthog.CaptureException(exception, "user_distinct_id");
}Add custom properties to include request, tenant, or domain context:
C#
PostHog AI
posthog.CaptureException(
exception,
"user_distinct_id",
new Dictionary<string, object>
{
["order_id"] = orderId,
["environment"] = "production",
}
);For the full setup guide, see the .NET error tracking installation docs.
Automatic exception capture is not available in the .NET SDK yet.
Person profiles and properties
The .NET SDK captures identified events by default. These create person profiles. To set person properties in these profiles, include them when capturing an event:
C#
PostHog AI
posthog.Capture(
"distinct_id",
"event_name",
personPropertiesToSet: new() { ["name"] = "Max Hedgehog" },
personPropertiesToSetOnce: new() { ["initial_url"] = "/blog" }
);For more details on the difference between $set and $set_once, see our person properties docs.
To capture anonymous events without person profiles, set the event's $process_person_profile property to false:
C#
PostHog AI
posthog.Capture(
"distinct_id",
"event_name",
properties: new() {
["$process_person_profile"] = false
}
)Alias
Sometimes, you want to assign multiple distinct IDs to a single user. This is helpful when your primary distinct ID is inaccessible. For example, if a distinct ID used on the frontend is not available in your backend.
In this case, you can use alias to assign another distinct ID to the same user.
C#
PostHog AI
await posthog.AliasAsync("current_distinct_id", "new_distinct_id");We strongly recommend reading our docs on alias to best understand how to correctly use this method.
Group analytics
Group analytics allows you to associate an event with a group (e.g. teams, organizations, etc.). Read the group analytics guide for more information.
Note: This is a paid feature and is not available on the open-source or free cloud plan. Learn more on our pricing page.
To capture an event and associate it with a group, add the groups argument to your Capture call:
C#
PostHog AI
posthog.Capture(
"user_distinct_id",
"some_event",
groups: [new Group("company", "company_id_in_your_db")]);Update properties on a group, use the GroupIdentifyAsync method:
C#
PostHog AI
await posthog.GroupIdentifyAsync(
type: "company",
key: "company_id_in_your_db",
name: "Awesome Inc.",
properties: new()
{
["employees"] = 11
}
);The name is a special property which is used in the PostHog UI for the name of the group. If you don't specify a name property, the group ID will be used instead.
Feature flags
PostHog's feature flags enable you to safely deploy and roll back new features as well as target specific users and groups with them.
There are two steps to implement feature flags in .NET:
Step 1: Evaluate flags once
Call EvaluateFlagsAsync() once for the user, then read values from the returned snapshot.
Boolean feature flags
C#
PostHog AI
var flags = await posthog.EvaluateFlagsAsync("distinct_id_of_your_user");
if (flags.IsEnabled("flag-key"))
{
// Do something differently for this user
// Optional: fetch the payload
var matchedPayload = flags.GetFlagPayload("flag-key");
}Multivariate feature flags
C#
PostHog AI
var flags = await posthog.EvaluateFlagsAsync("distinct_id_of_your_user");
var enabledVariant = flags.GetFlag("flag-key")?.VariantKey;
if (enabledVariant == "variant-key") // replace "variant-key" with the key of your variant
{
// Do something differently for this user
// Optional: fetch the payload
var matchedPayload = flags.GetFlagPayload("flag-key");
}flags.GetFlag() returns a nullable FeatureFlag object. Check VariantKey for multivariate flags and IsEnabled for boolean flags. It returns null when the flag wasn't returned by the evaluation.
Note:posthog.IsFeatureEnabledAsync(),posthog.GetFeatureFlagAsync(), andCapture(..., sendFeatureFlags: true, ...)still work during the migration period, but they're deprecated. PreferEvaluateFlagsAsync()for new code.
Step 2: Include feature flag information when capturing events
If you want use your feature flag to breakdown or filter events in your insights, you'll need to include feature flag information in those events. This ensures that the feature flag value is attributed correctly to the event.
Note: This step is only required for events captured using our server-side SDKs or API.
There are two methods you can use to include feature flag information in your events:
Method 1: Pass the evaluated flags snapshot to Capture()
Pass the same flags object that you used for branching. This attaches the exact flag values from that evaluation and doesn't make another /flags request.
C#
PostHog AI
var flags = await posthog.EvaluateFlagsAsync("distinct_id_of_your_user");
if (flags.IsEnabled("flag-key"))
{
// Do something differently for this user
}
posthog.Capture(
"distinct_id_of_your_user",
"event_name",
properties: null,
groups: null,
flags: flags
);By default, this attaches every flag in the snapshot using $feature/<flag-key> properties and $active_feature_flags.
To reduce event property bloat, pass a filtered snapshot:
C#
PostHog AI
// Attach only flags accessed with IsEnabled() or GetFlag() before this call
posthog.Capture(
"distinct_id_of_your_user",
"event_name",
properties: null,
groups: null,
flags: flags.OnlyAccessed()
);
// Attach only specific flags
posthog.Capture(
"distinct_id_of_your_user",
"event_name",
properties: null,
groups: null,
flags: flags.Only("checkout-flow", "new-dashboard")
);Method 2: Include the $feature/feature_flag_name property manually
In the event properties, include $feature/feature_flag_name: variant_key:
C#
PostHog AI
posthog.Capture(
"distinct_id_of_your_user",
"event_name",
properties: new()
{
// Replace feature-flag-key with your flag key and "variant-key" with the key of your variant
["$feature/feature-flag-key"] = "variant-key",
}
);Evaluating only specific flags
By default, EvaluateFlagsAsync() evaluates every flag for the user. If you only need a few flags, pass FlagKeysToEvaluate to request only those flags:
C#
PostHog AI
var flags = await posthog.EvaluateFlagsAsync(
"distinct_id_of_your_user",
options: new AllFeatureFlagsOptions
{
FlagKeysToEvaluate = new[] { "checkout-flow", "new-dashboard" },
}
);Sending $feature_flag_called events
Capturing $feature_flag_called events enables PostHog to know when a flag was accessed by a user and provide analytics and insights on the flag. With EvaluateFlagsAsync(), the SDK sends this event when you call flags.IsEnabled() or flags.GetFlag() for a flag.
The SDK deduplicates these events per (distinct_id, flag, value) in a local cache. If you reinitialize the PostHog client, the cache resets and $feature_flag_called events may be sent again. PostHog handles duplicates, so duplicate $feature_flag_called events don't affect your analytics.
flags.GetFlagPayload() doesn't send $feature_flag_called events and doesn't count as an access for OnlyAccessed().
Advanced: Overriding server properties
Sometimes, you may want to evaluate feature flags using person properties, groups, or group properties that haven't been ingested yet, or were set incorrectly earlier.
You can provide properties to evaluate the flag with by using the person properties, groups, and group properties arguments. PostHog will then use these values to evaluate the flag, instead of any properties currently stored on your PostHog server.
For example:
C#
PostHog AI
var flags = await posthog.EvaluateFlagsAsync(
"distinct_id_of_the_user",
options: new AllFeatureFlagsOptions
{
PersonProperties = new()
{
["property_name"] = "value",
},
Groups = new()
{
new Group("your_group_type", "your_group_id")
{
["group_property_name"] = "value",
},
new Group("another_group_type", "another_group_id")
{
["group_property_name"] = "another value",
},
},
}
);
if (flags.IsEnabled("flag-key"))
{
// Do something differently for this user
}Overriding GeoIP properties
By default, a user's GeoIP properties are set using the IP address they use to capture events on the frontend. You may want to override the these properties when evaluating feature flags. A common reason to do this is when you're not using PostHog on your frontend, so the user has no GeoIP properties.
You can override GeoIP properties by including them in the person_properties parameter when evaluating feature flags. This is useful when you're evaluating flags on your backend and want to use the client's location instead of your server's location.
The following GeoIP properties can be overridden:
-
$geoip_country_code -
$geoip_country_name -
$geoip_city_name -
$geoip_city_confidence -
$geoip_continent_code -
$geoip_continent_name -
$geoip_latitude -
$geoip_longitude -
$geoip_postal_code -
$geoip_subdivision_1_code -
$geoip_subdivision_1_name -
$geoip_subdivision_2_code -
$geoip_subdivision_2_name -
$geoip_subdivision_3_code -
$geoip_subdivision_3_name -
$geoip_time_zone
Simply include any of these properties in the person_properties parameter alongside your other person properties when calling feature flags.
Evaluation contexts
Configure evaluation contexts so this SDK only evaluates flags intended for the matching application, platform, or product area. For ASP.NET Core apps using PostHog.AspNetCore, add them to the PostHog configuration section:
JSON
PostHog AI
{
"PostHog": {
"ProjectToken": "<ph_project_token>",
"HostUrl": "https://us.i.posthog.com",
"EvaluationContexts": ["main-app", "api", "backend"]
}
}For code-based configuration, set EvaluationContexts on PostHogOptions:
C#
PostHog AI
var posthog = new PostHogClient(new PostHogOptions
{
ProjectToken = "<ph_project_token>",
HostUrl = new Uri("https://us.i.posthog.com"),
EvaluationContexts = ["main-app", "api", "backend"],
});Remote /flags requests from EvaluateFlagsAsync() include evaluation_contexts when configured.
For more details, see the evaluation contexts guide.
Local evaluation
Evaluating feature flags requires making a request to PostHog for each flag. However, you can improve performance by evaluating flags locally. Instead of making a request for each flag, PostHog will periodically request and store feature flag definitions locally, enabling you to evaluate flags without making additional requests.
It is best practice to use local evaluation flags when possible, since this enables you to resolve flags faster and with fewer API calls.
For details on how to implement local evaluation, see our local evaluation guide.
Experiments (A/B tests)
Since experiments use feature flags, the code for running an experiment is very similar to the feature flags code:
C#
PostHog AI
var flags = await posthog.EvaluateFlagsAsync("user_distinct_id");
var variant = flags.GetFlag("experiment-feature-flag-key")?.VariantKey;
if (variant == "variant-name")
{
// Do something
}It's also possible to run experiments without using feature flags.
AI observability
PostHog.AI adds AI observability for .NET applications using OpenAI or Azure OpenAI. It is currently pre-release, so expect breaking changes before a stable release.
For installation instructions, see the OpenAI guide for .NET or the Azure OpenAI guide for .NET.
GeoIP properties
The posthog-dotnet library disregards the server IP, does not add the GeoIP properties, and does not use the values for feature flag evaluations.
Serverless environments (Azure Functions/Render/Lambda/...)
By default, the library buffers events before sending them to the /batch endpoint for better performance. This can lead to lost events in serverless environments if the .NET process is terminated by the platform before the buffer is fully flushed.
To avoid this, call await posthog.FlushAsync() after processing every request by adding it as a middleware to your server. This allows posthog.Capture() to remain asynchronous for better performance.
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
Elixir Feature Flags installation - Docs
This library was built by the community but it's being maintained by the PostHog core team since v1.0.0. Thank you to Nick Kezhaya for building it originally. Thank you to Alex Martsinovich for contributing v2.0.0.
The package can be installed by adding posthog to your list of dependencies in mix.exs:
Elixir
PostHog AI
def deps do
[
{:posthog, "~> 2.0"}
]
endConfiguration
config/config.exs
PostHog AI
config :posthog,
enable: true,
api_host: "https://us.i.posthog.com",
api_key: "<ph_project_token>",
in_app_otp_apps: [:my_app]You can see all the available configuration options in the PostHog.Config module.
Optionally, you might want to enable the Plug integration to attach request metadata and tracing context in Plug-based applications including Phoenix. You still need to capture events explicitly with PostHog.capture/2 or PostHog.capture/3.
Development/Test mode
For a test environment, you can pass in test_mode: true value to the config. This causes events to be dropped instead of sent to PostHog.
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
Flask - Docs
PostHog makes it easy to get data about traffic and usage of your Flask app. Integrating PostHog enables analytics, custom events capture, feature flags, error tracking, and more.
This guide walks you through integrating PostHog into your Flask app using the Python SDK.
Installation
To start, run pip install posthog to install PostHog’s Python SDK.
Note: Version 7.x of the PostHog Python SDK requires Python 3.10 or higher.Then, initialize PostHog where you'd like to use it. For example, here's how to capture an event in a simple route:
app.py
PostHog AI
from flask import Flask
from posthog import Posthog
app = Flask(__name__)
posthog = Posthog(
'<ph_project_token>',
host='https://us.i.posthog.com',
)
@app.route('/api/dashboard', methods=['POST'])
def api_dashboard():
posthog.capture(
'dashboard_api_called',
distinct_id='distinct_id_of_your_user',
)
return '', 204You can find your project token and instance address in your project settings.
Identifying users
Identifying users is required. Backend events need adistinct_idthat matches the ID your frontend uses when callingposthog.identify(). Without this, backend events are orphaned — they can't be linked to frontend event captures, session replays, LLM traces, or error tracking.
>
See our guide on identifying users for how to set this up.
Request contexts
Use contexts to share identity, session IDs, and tags across multiple captures during a request.
If you're using PostHog JS on the frontend, configure `tracing_headers` for your Flask backend hostname so browser requests include the session and distinct ID headers.
Then read the incoming headers in your Flask request handler. Tracing headers are client-controlled analytics context, not authentication or authorization, so prefer your authenticated user ID when one is available:
Python
PostHog AI
from flask import request, session
from posthog import identify_context, set_context_session, tag
@app.route('/api/dashboard', methods=['POST'])
def api_dashboard():
with posthog.new_context(fresh=True):
distinct_id = session.get('user_id') or request.headers.get('X-POSTHOG-DISTINCT-ID')
if distinct_id:
identify_context(str(distinct_id))
session_id = request.headers.get('X-POSTHOG-SESSION-ID')
if session_id:
set_context_session(session_id)
tag('$current_url', request.url)
tag('$request_method', request.method)
tag('$request_path', request.path)
posthog.capture('dashboard_api_called')
return '', 204Events captured without a context or explicit distinct_id are sent as anonymous events with an auto-generated distinct_id. See the Python SDK docs for more details.
Error tracking
Flask has built-in error handlers. This means PostHog’s default exception autocapture won’t work and we need to manually capture errors instead using capture_exception():
Python
PostHog AI
from flask import Flask, jsonify
from posthog import Posthog
app = Flask(__name__)
posthog = Posthog('<ph_project_token>', host='https://us.i.posthog.com')
@app.errorhandler(Exception)
def handle_exception(e):
# Capture methods, including capture_exception, return the UUID of the captured event,
# which you can use to find specific errors users encountered
event_id = posthog.capture_exception(e)
# You can show the event ID to your user, and ask them to include it in bug reports
response = jsonify({'message': str(e), 'error_id': event_id})
response.status_code = 500
return responseNext steps
For any technical questions for how to integrate specific PostHog features into Flask (such as analytics, feature flags, A/B testing, etc.), have a look at our Python SDK docs.
Alternatively, the following tutorials can help you get started:
- How to set up analytics in Python and Flask
- How to set up feature flags in Python and Flask
- How to set up A/B tests in Python and Flask
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
Flutter Feature Flags installation - Docs
1. 1
Install the package
Required
Add the PostHog Flutter SDK to your pubspec.yaml:
pubspec.yaml
PostHog AI
posthog_flutter: ^5.24.02. 2
Platform setup
Required
Tab
Add these values to your AndroidManifest.xml:
android/app/src/main/AndroidManifest.xml
PostHog AI
<application>
<activity>
[...]
</activity>
<meta-data android:name="com.posthog.posthog.PROJECT_TOKEN" android:value="<ph_project_token>" />
<meta-data android:name="com.posthog.posthog.POSTHOG_HOST" android:value="https://us.i.posthog.com" />
<meta-data android:name="com.posthog.posthog.TRACK_APPLICATION_LIFECYCLE_EVENTS" android:value="true" />
<meta-data android:name="com.posthog.posthog.DEBUG" android:value="true" />
</application>Update the minimum Android SDK version to 21 in android/app/build.gradle:
android/app/build.gradle
PostHog AI
defaultConfig {
minSdkVersion 23
// rest of your config
}Tab
Add these values to your Info.plist:
ios/Runner/Info.plist
PostHog AI
<dict>
[...]
<key>com.posthog.posthog.PROJECT_TOKEN</key>
<string><ph_project_token></string>
<key>com.posthog.posthog.POSTHOG_HOST</key>
<string>https://us.i.posthog.com</string>
<key>com.posthog.posthog.CAPTURE_APPLICATION_LIFECYCLE_EVENTS</key>
<true/>
<key>com.posthog.posthog.DEBUG</key>
<true/>
</dict>Update the minimum platform version to iOS 13.0 in your Podfile:
Podfile
PostHog AI
platform :ios, '13.0'
# rest of your configTab
Add these values in index.html:
web/index.html
PostHog AI
<!DOCTYPE html>
<html>
<head>
...
<script>
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init capture register register_once register_for_session unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled getFeatureFlag getFeatureFlagPayload reloadFeatureFlags group identify setPersonProperties setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags resetGroups onFeatureFlags addFeatureFlagsHandler onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey getNextSurveyStep".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
posthog.init('<ph_project_token>', {
api_host: 'https://us.i.posthog.com',
defaults: '2026-01-30',
})
</script>
</head>
<body>
...
</body>
</html>3. 3
Send events
Recommended
Once installed, PostHog will automatically start capturing events. You can also manually send events to test your integration:
Dart
PostHog AI
import 'package:posthog_flutter/posthog_flutter.dart';
await Posthog().capture(
eventName: 'button_clicked',
properties: {
'button_name': 'signup'
}
);4. 4
Evaluate boolean feature flags
Required
Check if a feature flag is enabled:
Dart
PostHog AI
final isMyFlagEnabled = await Posthog().isFeatureEnabled('flag-key');
if (isMyFlagEnabled) {
// Do something differently for this user
// Optional: fetch the payload
final matchedFlagPayload = (await Posthog().getFeatureFlagResult('flag-key'))?.payload;
}5. 5
Evaluate multivariate feature flags
Optional
For multivariate flags, check which variant the user has been assigned:
Dart
PostHog AI
final enabledVariant = await Posthog().getFeatureFlag('flag-key');
if (enabledVariant == 'variant-key') { // replace 'variant-key' with the key of your variant
// Do something differently for this user
// Optional: fetch the payload
final matchedFlagPayload = (await Posthog().getFeatureFlagResult('flag-key'))?.payload;
}6. 6
Running experiments
Optional
Experiments run on top of our feature flags. Once you've implemented the flag in your code, you run an experiment by creating a new experiment in the PostHog dashboard.
7. 7
Next steps
Recommended
Now that you're evaluating flags, continue with the resources below to learn what else Feature Flags enables within the PostHog platform.
| Resource | Description |
|---|---|
| Creating a feature flag | How to create a feature flag in PostHog |
| Adding feature flag code | How to check flags in your code for all platforms |
| Framework-specific guides | Setup guides for React Native, Next.js, Flutter, and other frameworks |
| How to do a phased rollout | Gradually roll out features to minimize risk |
| More tutorials | Other real-world examples and use cases |
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
Go Feature Flags installation - Docs
1. 1
Install the package
Required
Install the PostHog Go library:
Terminal
PostHog AI
go get "github.com/posthog/posthog-go"2. 2
Configure PostHog
Required
Initialize the PostHog client with your project token and host:
main.go
PostHog AI
package main
import (
"github.com/posthog/posthog-go"
)
func main() {
client, _ := posthog.NewWithConfig("<ph_project_token>", posthog.Config{Endpoint: "https://us.i.posthog.com"})
defer client.Close()
}3. 3
Send events
Recommended
Once installed, you can manually send events to test your integration:
Go
PostHog AI
client.Enqueue(posthog.Capture{
DistinctId: "user_123",
Event: "button_clicked",
Properties: posthog.NewProperties().
Set("button_name", "signup"),
})4. 4
Evaluate boolean feature flags
Required
Check if a feature flag is enabled:
isMyFlagEnabled, err := client.IsFeatureEnabled(posthog.FeatureFlagPayload{
Key: "flag-key",
DistinctId: "distinct_id_of_your_user",
})
if err != nil {
// Handle error (e.g. capture error and fallback to default behaviour)
}
if isMyFlagEnabled == true {
// Do something differently for this user
}5. 5
Evaluate multivariate feature flags
Optional
For multivariate flags, check which variant the user has been assigned:
enabledVariant, err := client.GetFeatureFlag(posthog.FeatureFlagPayload{
Key: "flag-key",
DistinctId: "distinct_id_of_your_user",
})
if err != nil {
// Handle error (e.g. capture error and fallback to default behaviour)
}
if enabledVariant == "variant-key" { // replace 'variant-key' with the key of your variant
// Do something differently for this user
}6. 6
Include feature flag information in events
Required
If you want to use your feature flag to breakdown or filter events in your insights, you'll need to include feature flag information in those events. This ensures that the feature flag value is attributed correctly to the event.
Note: This step is only required for events captured using our server-side SDKs or API.
Set SendFeatureFlags (recommended)
Set SendFeatureFlags to true in your capture call:
Go
PostHog AI
client.Enqueue(posthog.Capture{
DistinctId: "distinct_id_of_your_user",
Event: "event_name",
SendFeatureFlags: true,
})Include $feature property
Include the $feature/feature_flag_name property in your event properties:
Go
PostHog AI
client.Enqueue(posthog.Capture{
DistinctId: "distinct_id_of_your_user",
Event: "event_name",
Properties: posthog.NewProperties().
Set("$feature/feature-flag-key", "variant-key"), // replace feature-flag-key with your flag key. Replace 'variant-key' with the key of your variant
})7. 7
Override server properties
Optional
Sometimes, you may want to evaluate feature flags using properties that haven't been ingested yet, or were set incorrectly earlier. You can provide properties to evaluate the flag with:
enabledVariant, err := client.GetFeatureFlag(
FeatureFlagPayload{
Key: "flag-key",
DistinctId: "distinct_id_of_the_user",
Groups: posthog.NewGroups().
Set("your_group_type", "your_group_id").
Set("another_group_type", "your_group_id"),
PersonProperties: posthog.NewProperties().
Set("property_name", "value"),
GroupProperties: map[string]map[string]interface{}{
"your_group_type": {
"group_property_name": "value",
},
"another_group_type": {
"group_property_name": "value",
},
},
},
)8. 8
Running experiments
Optional
Experiments run on top of our feature flags. Once you've implemented the flag in your code, you run an experiment by creating a new experiment in the PostHog dashboard.
9. 9
Next steps
Recommended
Now that you're evaluating flags, continue with the resources below to learn what else Feature Flags enables within the PostHog platform.
| Resource | Description |
|---|---|
| Creating a feature flag | How to create a feature flag in PostHog |
| Adding feature flag code | How to check flags in your code for all platforms |
| Framework-specific guides | Setup guides for React Native, Next.js, Flutter, and other frameworks |
| How to do a phased rollout | Gradually roll out features to minimize risk |
| More tutorials | Other real-world examples and use cases |
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
iOS Feature Flags installation - Docs
1. 1
Install dependency
Required
Install via Swift Package Manager:
Package.swift
PostHog AI
dependencies: [
.package(url: "https://github.com/PostHog/posthog-ios.git", from: "3.56.0")
]Or add PostHog to your Podfile:
Podfile
PostHog AI
pod "PostHog", "~> 3.56"2. 2
Configure PostHog
Required
Initialize PostHog in your AppDelegate:
AppDelegate.swift
PostHog AI
import Foundation
import PostHog
import UIKit
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
let POSTHOG_PROJECT_TOKEN = "<ph_project_token>"
let POSTHOG_HOST = "https://us.i.posthog.com"
let config = PostHogConfig(projectToken: POSTHOG_PROJECT_TOKEN, host: POSTHOG_HOST)
PostHogSDK.shared.setup(config)
return true
}
}3. 3
Send events
Recommended
Once installed, PostHog will automatically start capturing events. You can also manually send events to test your integration:
Swift
PostHog AI
PostHogSDK.shared.capture("button_clicked", properties: ["button_name": "signup"])4. 4
Evaluate boolean feature flags
Required
Check if a feature flag is enabled:
Swift
PostHog AI
let isMyFlagEnabled = PostHogSDK.shared.isFeatureEnabled("flag-key")
if isMyFlagEnabled {
// Do something differently for this user
// Optional: fetch the payload
let matchedFlagPayload = PostHogSDK.shared.getFeatureFlagResult("flag-key")?.payload
}5. 5
Evaluate multivariate feature flags
Optional
For multivariate flags, check which variant the user has been assigned:
Swift
PostHog AI
let enabledVariant = PostHogSDK.shared.getFeatureFlag("flag-key")
if enabledVariant == "variant-key" { // replace 'variant-key' with the key of your variant
// Do something differently for this user
// Optional: fetch the payload
let matchedFlagPayload = PostHogSDK.shared.getFeatureFlagResult("flag-key")?.payload
}6. 6
Running experiments
Optional
Experiments run on top of our feature flags. Once you've implemented the flag in your code, you run an experiment by creating a new experiment in the PostHog dashboard.
7. 7
Next steps
Recommended
Now that you're evaluating flags, continue with the resources below to learn what else Feature Flags enables within the PostHog platform.
| Resource | Description |
|---|---|
| Creating a feature flag | How to create a feature flag in PostHog |
| Adding feature flag code | How to check flags in your code for all platforms |
| Framework-specific guides | Setup guides for React Native, Next.js, Flutter, and other frameworks |
| How to do a phased rollout | Gradually roll out features to minimize risk |
| More tutorials | Other real-world examples and use cases |
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
Java Feature Flags installation - Docs
The best way to install the PostHog Java SDK is with a build system like Gradle or Maven. This ensures you can easily upgrade to the latest versions.
Look up the latest version of `com.posthog.posthog-server`.
Gradle
All you need to do is add the posthog-server module to your build.gradle:
build.gradle
PostHog AI
dependencies {
implementation 'com.posthog:posthog-server:2.+'
}Maven
All you need to do is add the posthog-server module to your pom.xml:
pom.xml
PostHog AI
<dependency>
<groupId>com.posthog</groupId>
<artifactId>posthog-server</artifactId>
<version>LATEST</version>
</dependency>Other
See `com.posthog.posthog-server` in the Maven Central Repository. Clicking on the latest version shows you options for adding dependencies for other build systems.
Setup
Java
PostHog AI
import com.posthog.server.PostHog;
import com.posthog.server.PostHogConfig;
import com.posthog.server.PostHogInterface;
class Sample {
private static final String POSTHOG_API_KEY = "<ph_project_token>";
private static final String POSTHOG_HOST = "https://us.i.posthog.com";
public static void main(String args[]) {
PostHogConfig config = PostHogConfig
.builder(POSTHOG_API_KEY)
.host(POSTHOG_HOST)
.build();
PostHogInterface posthog = PostHog.with(config);
posthog.flush(); // send any remaining events
posthog.close(); // shut down the client
}
}Integrating with Spring
To see how to integrate the PostHog SDK with Spring, check out this sample project.
Debug mode
If you're not seeing the expected events being captured, or the feature flags being evaluated, you can enable debug mode to see what's happening.
To see detailed logging, set the debug configuration option to true.
Java
PostHog AI
PostHogConfig config = PostHogConfig
.builder(POSTHOG_API_KEY)
.host(POSTHOG_HOST)
.debug(true)
.build();Community questions
Ask a question
Was this page useful?
HelpfulCould be better
Laravel - Docs
PostHog integrates with Laravel through the PostHog PHP SDK. This page covers Laravel-specific setup. For SDK features such as event capture, identifying users, feature flags, group analytics, and configuration options, see the PHP SDK docs.
Installation
Install the PHP SDK as described in the PHP installation guide, then add your project token and host to .env:
.env
PostHog AI
POSTHOG_API_KEY=<ph_project_token>
POSTHOG_HOST=https://us.i.posthog.comAdd PostHog to Laravel's services config:
config/services.php
PostHog AI
'posthog' => [
'api_key' => env('POSTHOG_API_KEY'),
'host' => env('POSTHOG_HOST', 'https://us.i.posthog.com'),
],Initialize PostHog in the boot method of app/Providers/AppServiceProvider.php:
app/Providers/AppServiceProvider.php
PostHog AI
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use PostHog\PostHog;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
if (! config('services.posthog.api_key')) {
return;
}
PostHog::init(
config('services.posthog.api_key'),
[
'host' => config('services.posthog.host'),
]
);
}
}Request context middleware
Client SDKs such as PostHog JS can send tracing headers to your Laravel backend. Configure `tracing_headers` for your Laravel backend hostname so browser requests include the session and distinct ID headers.
The PHP SDK can read X-PostHog-Distinct-Id and X-PostHog-Session-Id headers and apply them to events captured during the request. Tracing headers are client-controlled analytics context, not authentication or authorization. For security-sensitive server-side events or decisions, pass an authenticated distinctId explicitly, such as auth()->id(). For the lower-level context APIs, see the PHP request context docs.
Add middleware like this:
app/Http/Middleware/PostHogRequestContext.php
PostHog AI
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use PostHog\PostHog;
use Symfony\Component\HttpFoundation\Response;
final class PostHogRequestContext
{
public function handle(Request $request, Closure $next): Response
{
if (! config('services.posthog.api_key')) {
return $next($request);
}
$context = PostHog::contextFromHeaders($request->headers->all());
$context['properties'] = array_merge(
$context['properties'] ?? [],
array_filter([
'$current_url' => $request->fullUrl(),
'$request_method' => $request->method(),
'$request_path' => $request->getPathInfo(),
'$user_agent' => $request->userAgent(),
'$ip' => $request->ip(),
], static fn ($value): bool => $value !== null && $value !== '')
);
return PostHog::withContext(
$context,
static fn (): Response => $next($request),
['fresh' => true]
);
}
}Register this middleware using your Laravel version's normal middleware registration.
Error tracking in Laravel
The PHP SDK supports error tracking, but Laravel handles most request exceptions before they become uncaught PHP exceptions. Capture Laravel-reported exceptions explicitly.
In Laravel 11 and later, add a report callback in bootstrap/app.php:
bootstrap/app.php
PostHog AI
use Illuminate\Foundation\Configuration\Exceptions;
use PostHog\PostHog;
use Throwable;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->report(function (Throwable $e): void {
if (! config('services.posthog.api_key')) {
return;
}
PostHog::captureException(
$e,
auth()->id() !== null ? (string) auth()->id() : null,
[
'$current_url' => request()->fullUrl(),
'$request_method' => request()->method(),
]
);
});
})For older Laravel versions, call PostHog::captureException() from your exception handler's report method.
Long-running processes
In normal PHP request lifecycles, queued events flush when the client is destroyed. In long-running Laravel processes such as queue workers, Horizon, or Octane, call PostHog::flush() after capturing important events or at the end of a job/request.
If you prefer immediate delivery in queue workers, configure the PHP SDK with batch_size set to 1 for those workers:
PHP
PostHog AI
PostHog::init(
'<ph_project_token>',
[
'host' => config('services.posthog.host'),
'batch_size' => 1,
]
);Next steps
See the PHP SDK docs for usage examples and the full API reference.
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
Next.js - Docs
PostHog makes it easy to get data about traffic and usage of your Next.js app. Integrating PostHog into your site enables analytics about user behavior, custom events capture, session recordings, feature flags, and more.
This guide walks you through integrating PostHog into your Next.js app using the React and the Node.js SDKs.
You can see a working example of this integration in our Next.js demo app.
Next.js has both client and server-side rendering, as well as pages and app routers. We'll cover all of these options in this guide.
Try `@posthog/next` (pre-release): A simplified Next.js integration with synchronized client/server identity, server-side flag bootstrapping, and a built-in API proxy. Read the setup guide →
Prerequisites
To follow this guide along, you need:
1. A PostHog instance (either Cloud or self-hosted) 2. A Next.js application
Beta: integration via LLM
Install PostHog for Next.js in seconds with our wizard by running this prompt with LLM coding agents like Cursor and Bolt, or by running it in your terminal.
npx @posthog/wizard@latest
Or, to integrate manually, continue with the rest of this guide.
Client-side setup
Install posthog-js using your package manager:
PostHog AI
npm
npm install --save posthog-jsYarn
yarn add posthog-jspnpm
pnpm add posthog-jsBun
bun add posthog-jsAdd your environment variables to your .env.local file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token in your project settings.
.env.local
PostHog AI
NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN=<ph_project_token>
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.comThese values need to start with NEXT_PUBLIC_ to be accessible on the client-side.
Integration
Next.js provides the `instrumentation-client.ts|js` file for client-side setup. Add it to the root of your Next.js app (for both app and pages router) and initialize PostHog in it like this:
PostHog AI
instrumentation-client.js
import posthog from 'posthog-js'
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
defaults: '2026-01-30'
});instrumentation-client.ts
import posthog from 'posthog-js'
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN!, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
defaults: '2026-01-30'
});Bootstrapping with instrumentation-client
When using instrumentation-client, the values you pass to posthog.init remain fixed for the entire session. This means bootstrapping only works if you evaluate flags before your app renders (for example, on the server).
If you need flag values after the app has rendered, you’ll want to:
- Evaluate the flag on the server and pass the value into your app, or
- Evaluate the flag in an earlier page/state, then store and re-use it when needed.
Both approaches avoid flicker and give you the same outcome as bootstrapping, as long as you use the same distinct_id across client and server.
See the bootstrapping guide for more information.
Identifying users
Identifying users is required. Call posthog.identify('your-user-id') after login to link events to a known user. This is what connects frontend event captures, session replays, LLM traces, and error tracking to the same person — and lets backend events link back too.>
See our guide on identifying users for how to set this up.
Set up a reverse proxy (recommended)
We recommend setting up a reverse proxy, so that events are less likely to be intercepted by tracking blockers.
We have our own managed reverse proxy service, which is free for all PostHog Cloud users, routes through our infrastructure, and makes setting up your proxy easy.
If you don't want to use our managed service then there are several other options for creating a reverse proxy, including using Cloudflare, AWS Cloudfront, and Vercel.
Grouping products in one project (recommended)
If you have multiple customer-facing products (e.g. a marketing website + mobile app + web app), it's best to install PostHog on them all and group them in one project.
This makes it possible to track users across their entire journey (e.g. from visiting your marketing website to signing up for your product), or how they use your product across multiple platforms.
Add IPs to Firewall/WAF allowlists (recommended)
For certain features like heatmaps, your Web Application Firewall (WAF) may be blocking PostHog’s requests to your site. Add these IP addresses to your WAF allowlist or rules to let PostHog access your site.
EU: 3.75.65.221, 18.197.246.42, 3.120.223.253
US: 44.205.89.55, 52.4.194.122, 44.208.188.173
These are public, stable IPs used by PostHog services (e.g., Celery tasks for snapshots).
Accessing PostHog
Once initialized in instrumentation-client.js|ts, import posthog from posthog-js anywhere and call the methods you need on the posthog object.
JavaScript
PostHog AI
"use client";
import posthog from "posthog-js";
export default function Home() {
return (
<div>
<button onClick={() => posthog.capture("test_event")}>Click me for an event</button>
</div>
);
}Using React hooks
The React feature flag hooks work automatically when PostHog is initialized via instrumentation-client.ts. The hooks use the initialized posthog-js singleton:
JavaScript
PostHog AI
"use client";
import { useFeatureFlagEnabled } from "@posthog/react";
export default function FeatureComponent() {
const showNewFeature = useFeatureFlagEnabled("new-feature");
return showNewFeature ? <NewFeature /> : <OldFeature />;
}Usage
See the React SDK docs for examples of how to use:
- `posthog-js` functions like custom event capture, user identification, and more.
- Feature flags including variants and payloads.
You can also read the full `posthog-js` documentation for all the usable functions.
Server-side analytics
Next.js enables you to both server-side render pages and add server-side functionality. To integrate PostHog into your Next.js app on the server-side, you can use the Node SDK.
First, install the posthog-node library:
PostHog AI
npm
npm install posthog-node --saveYarn
yarn add posthog-nodepnpm
pnpm add posthog-nodeBun
bun add posthog-nodeRouter-specific instructions
App router
For the app router, we can initialize the posthog-node SDK once with a PostHogClient function, and import it into files.
This enables us to send events and fetch data from PostHog on the server – without making client-side requests.
JavaScript
PostHog AI
// app/posthog.js
import { PostHog } from 'posthog-node'
export default function PostHogClient() {
const posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN, {
host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
flushAt: 1,
flushInterval: 0
})
return posthogClient
}Note: Because server-side functions in Next.js can be short-lived, we setflushAtto1andflushIntervalto0.
>
- flushAt sets how many capture calls we should flush the queue (in one batch).-flushIntervalsets how many milliseconds we should wait before flushing the queue. Setting them to the lowest number ensures events are sent immediately and not batched. We also need to callawait posthog.shutdown()once done.
To use this client, we import it into our pages and call it with the PostHogClient function:
JavaScript
PostHog AI
import Link from 'next/link'
import PostHogClient from '../posthog'
export default async function About() {
const posthog = PostHogClient()
const flags = await posthog.getAllFlags(
'user_distinct_id' // replace with a user's distinct ID
);
await posthog.shutdown()
return (
<main>
<h1>About</h1>
<Link href="/">Go home</Link>
{ flags['main-cta'] &&
<Link href="http://posthog.com/">Go to PostHog</Link>
}
</main>
)
}Pages router
For the pages router, we can use the getServerSideProps function to access PostHog on the server-side, send events, evaluate feature flags, and more.
This looks like this:
JavaScript
PostHog AI
// pages/posts/[id].js
import { useContext, useEffect, useState } from 'react'
import { getServerSession } from "next-auth/next"
import { PostHog } from 'posthog-node'
export default function Post({ post, flags }) {
const [ctaState, setCtaState] = useState()
useEffect(() => {
if (flags) {
setCtaState(flags['blog-cta'])
}
})
return (
<div>
<h1>{post.title}</h1>
<p>By: {post.author}</p>
<p>{post.content}</p>
{ctaState &&
<p><a href="/">Go to PostHog</a></p>
}
<button onClick={likePost}>Like</button>
</div>
)
}
export async function getServerSideProps(ctx) {
const session = await getServerSession(ctx.req, ctx.res)
let flags = null
if (session) {
const client = new PostHog(
process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN,
{
host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
}
)
flags = await client.getAllFlags(session.user.email);
client.capture({
distinctId: session.user.email,
event: 'loaded blog article',
properties: {
$current_url: ctx.req.url,
},
});
await client.shutdown()
}
const { posts } = await import('../../blog.json')
const post = posts.find((post) => post.id.toString() === ctx.params.id)
return {
props: {
post,
flags
},
}
}Note: Make sure to always call await client.shutdown() after sending events from the server-side. PostHog queues events into larger batches, and this call forces all batched events to be flushed immediately.Server-side configuration
Next.js overrides the default fetch behavior on the server to introduce their own cache. PostHog ignores that cache by default, as this is Next.js's default behavior for any fetch call.
You can override that configuration when initializing PostHog, but make sure you understand the pros/cons of using Next.js's cache and that you might get cached results rather than the actual result our server would return. This is important for feature flags, for example.
TSX
PostHog AI
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN, {
// ... your configuration
fetch_options: {
cache: 'force-cache', // Use Next.js cache
next_options: { // Passed to the `next` option for `fetch`
revalidate: 60, // Cache for 60 seconds
tags: ['posthog'], // Can be used with Next.js `revalidateTag` function
},
}
})Configuring a reverse proxy to PostHog
To improve the reliability of client-side tracking and make requests less likely to be intercepted by tracking blockers, you can setup a reverse proxy in Next.js. Read more about deploying a reverse proxy using Next.js rewrites, Next.js middleware, and Vercel rewrites.
Further reading
- How to set up Next.js analytics, feature flags, and more
- How to set up Next.js pages router analytics, feature flags, and more
- How to set up Next.js A/B tests
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
Node.js Feature Flags installation - Docs
1. 1
Install the package
Required
Install the PostHog Node.js library using your package manager:
PostHog AI
npm
npm install posthog-nodeyarn
yarn add posthog-nodepnpm
pnpm add posthog-node2. 2
Initialize PostHog
Required
Initialize the PostHog client with your project token:
Node.js
PostHog AI
import { PostHog } from 'posthog-node'
const client = new PostHog(
'<ph_project_token>',
{
host: 'https://us.i.posthog.com'
}
)3. 3
Send an event
Recommended
Once installed, you can manually send events to test your integration:
Node.js
PostHog AI
client.capture({
distinctId: 'distinct_id_of_the_user',
event: 'event_name',
properties: {
property1: 'value',
property2: 'value',
},
})4. 4
Evaluate boolean feature flags
Required
Check if a feature flag is enabled:
const isFeatureFlagEnabled = await client.isFeatureEnabled('flag-key', 'distinct_id_of_your_user')
if (isFeatureFlagEnabled) {
// Your code if the flag is enabled
// Optional: fetch the payload
const matchedFlagPayload = await client.getFeatureFlagPayload('flag-key', 'distinct_id_of_your_user', isFeatureFlagEnabled)
}5. 5
Evaluate multivariate feature flags
Optional
For multivariate flags, check which variant the user has been assigned:
const enabledVariant = await client.getFeatureFlag('flag-key', 'distinct_id_of_your_user')
if (enabledVariant === 'variant-key') { // replace 'variant-key' with the key of your variant
// Do something differently for this user
// Optional: fetch the payload
const matchedFlagPayload = await client.getFeatureFlagPayload('flag-key', 'distinct_id_of_your_user', enabledVariant)
}6. 6
Include feature flag information in events
Required
If you want to use your feature flag to breakdown or filter events in your insights, you'll need to include feature flag information in those events. This ensures that the feature flag value is attributed correctly to the event.
Note: This step is only required for events captured using our server-side SDKs or API.
Set sendFeatureFlags (recommended)
Set sendFeatureFlags to true in your capture call:
Node.js
PostHog AI
client.capture({
distinctId: 'distinct_id_of_your_user',
event: 'event_name',
sendFeatureFlags: true,
})Include $feature property
Include the $feature/feature_flag_name property in your event properties:
Node.js
PostHog AI
client.capture({
distinctId: 'distinct_id_of_your_user',
event: 'event_name',
properties: {
'$feature/feature-flag-key': 'variant-key' // replace feature-flag-key with your flag key. Replace 'variant-key' with the key of your variant
},
})7. 7
Override server properties
Optional
Sometimes, you may want to evaluate feature flags using properties that haven't been ingested yet, or were set incorrectly earlier. You can provide properties to evaluate the flag with:
await client.getFeatureFlag(
'flag-key',
'distinct_id_of_the_user',
{
personProperties: {
'property_name': 'value'
},
groups: {
"your_group_type": "your_group_id",
"another_group_type": "your_group_id",
},
groupProperties: {
'your_group_type': {
'group_property_name': 'value'
},
'another_group_type': {
'group_property_name': 'value'
},
},
}
)8. 8
Running experiments
Optional
Experiments run on top of our feature flags. Once you've implemented the flag in your code, you run an experiment by creating a new experiment in the PostHog dashboard.
9. 9
Next steps
Recommended
Now that you're evaluating flags, continue with the resources below to learn what else Feature Flags enables within the PostHog platform.
| Resource | Description |
|---|---|
| Creating a feature flag | How to create a feature flag in PostHog |
| Adding feature flag code | How to check flags in your code for all platforms |
| Framework-specific guides | Setup guides for React Native, Next.js, Flutter, and other frameworks |
| How to do a phased rollout | Gradually roll out features to minimize risk |
| More tutorials | Other real-world examples and use cases |
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
PHP Feature Flags installation - Docs
1. 1
Install the package
Required
Install the PostHog PHP library using Composer:
Terminal
PostHog AI
composer require posthog/posthog-php2. 2
Configure PostHog
Required
Initialize the PostHog client with your project token and host:
PHP
PostHog AI
PostHog\PostHog::init(
'<ph_project_token>',
['host' => 'https://us.i.posthog.com']
);3. 3
Send events
Recommended
Once installed, you can manually send events to test your integration:
PHP
PostHog AI
PostHog::capture([
'distinctId' => 'test-user',
'event' => 'test-event',
]);4. 4
Evaluate boolean feature flags
Required
Check if a feature flag is enabled:
$isMyFlagEnabledForUser = PostHog::isFeatureEnabled('flag-key', 'distinct_id_of_your_user')
if ($isMyFlagEnabledForUser) {
// Do something differently for this user
}5. 5
Evaluate multivariate feature flags
Optional
For multivariate flags, check which variant the user has been assigned:
$enabledVariant = PostHog::getFeatureFlag('flag-key', 'distinct_id_of_your_user')
if ($enabledVariant === 'variant-key') { # replace 'variant-key' with the key of your variant
# Do something differently for this user
}6. 6
Include feature flag information in events
Required
If you want to use your feature flag to breakdown or filter events in your insights, you'll need to include feature flag information in those events. This ensures that the feature flag value is attributed correctly to the event.
Note: This step is only required for events captured using our server-side SDKs or API.
Set send_feature_flags (recommended)
Set send_feature_flags to true in your capture call:
PHP
PostHog AI
PostHog::capture(array(
'distinctId' => 'distinct_id_of_your_user',
'event' => 'event_name',
'send_feature_flags' => true
));Include $feature property
Include the $feature/feature_flag_name property in your event properties:
PHP
PostHog AI
PostHog::capture(array(
'distinctId' => 'distinct_id_of_your_user',
'event' => 'event_name',
'properties' => array(
'$feature/feature-flag-key' => 'variant-key' // replace feature-flag-key with your flag key. Replace 'variant-key' with the key of your variant
)
));7. 7
Override server properties
Optional
Sometimes, you may want to evaluate feature flags using properties that haven't been ingested yet, or were set incorrectly earlier. You can provide properties to evaluate the flag with:
PostHog::getFeatureFlag(
'flag-key',
'distinct_id_of_the_user',
[
'your_group_type' => 'your_group_id',
'another_group_type' => 'your_group_id'
], // groups
['property_name' => 'value'], // person properties
[
'your_group_type' => ['group_property_name' => 'value'],
'another_group_type' => ['group_property_name' => 'value']
], // group properties
false, // onlyEvaluateLocally, Optional. Defaults to false.
true // sendFeatureFlagEvents
)8. 8
Running experiments
Optional
Experiments run on top of our feature flags. Once you've implemented the flag in your code, you run an experiment by creating a new experiment in the PostHog dashboard.
9. 9
Next steps
Recommended
Now that you're evaluating flags, continue with the resources below to learn what else Feature Flags enables within the PostHog platform.
| Resource | Description |
|---|---|
| Creating a feature flag | How to create a feature flag in PostHog |
| Adding feature flag code | How to check flags in your code for all platforms |
| Framework-specific guides | Setup guides for React Native, Next.js, Flutter, and other frameworks |
| How to do a phased rollout | Gradually roll out features to minimize risk |
| More tutorials | Other real-world examples and use cases |
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
Python Feature Flags installation - Docs
1. 1
Install the package
Required
Install the PostHog Python library using pip:
Terminal
PostHog AI
pip install posthog2. 2
Initialize PostHog
Required
Initialize the PostHog client with your project token and host from your project settings:
Python
PostHog AI
from posthog import Posthog
posthog = Posthog(
project_api_key='<ph_project_token>',
host='https://us.i.posthog.com'
)Django integration
If you're using Django, check out our Django integration for automatic request tracking.
3. 3
Send events
Recommended
Once installed, PostHog will automatically start capturing events. You can also manually send events to test your integration:
Capture custom events by calling the capture method with an event name and properties:
Python
PostHog AI
import posthog
posthog.capture('user_signed_up', distinct_id='user_123', properties={'example_property': 'example_value'})4. 4
Evaluate boolean feature flags
Required
Check if a feature flag is enabled:
is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user')
if is_my_flag_enabled:
# Do something differently for this user
# Optional: fetch the payload
matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')5. 5
Evaluate multivariate feature flags
Optional
For multivariate flags, check which variant the user has been assigned:
enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user')
if enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant
# Do something differently for this user
# Optional: fetch the payload
matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')6. 6
Include feature flag information in events
Required
If you want to use your feature flag to breakdown or filter events in your insights, you'll need to include feature flag information in those events. This ensures that the feature flag value is attributed correctly to the event.
Note: This step is only required for events captured using our server-side SDKs or API.
Set send_feature_flags (recommended)
Set send_feature_flags to True in your capture call:
Python
PostHog AI
posthog.capture(
distinct_id="distinct_id_of_the_user",
event='event_name',
send_feature_flags=True
)Include $feature property
Include the $feature/feature_flag_name property in your event properties:
Python
PostHog AI
posthog.capture(
"event_name",
distinct_id="distinct_id_of_the_user",
properties={
"$feature/feature-flag-key": "variant-key" # replace feature-flag-key with your flag key. Replace 'variant-key' with the key of your variant
},
)7. 7
Override server properties
Optional
Sometimes, you may want to evaluate feature flags using properties that haven't been ingested yet, or were set incorrectly earlier. You can provide properties to evaluate the flag with:
posthog.get_feature_flag(
'flag-key',
'distinct_id_of_the_user',
person_properties={'property_name': 'value'},
groups={
'your_group_type': 'your_group_id',
'another_group_type': 'your_group_id'},
group_properties={
'your_group_type': {'group_property_name': 'value'},
'another_group_type': {'group_property_name': 'value'}
},
)8. 8
Running experiments
Optional
Experiments run on top of our feature flags. Once you've implemented the flag in your code, you run an experiment by creating a new experiment in the PostHog dashboard.
9. 9
Next steps
Recommended
Now that you're evaluating flags, continue with the resources below to learn what else Feature Flags enables within the PostHog platform.
| Resource | Description |
|---|---|
| Creating a feature flag | How to create a feature flag in PostHog |
| Adding feature flag code | How to check flags in your code for all platforms |
| Framework-specific guides | Setup guides for React Native, Next.js, Flutter, and other frameworks |
| How to do a phased rollout | Gradually roll out features to minimize risk |
| More tutorials | Other real-world examples and use cases |
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
React Native Feature Flags installation - Docs
1. 1
Install the package
Required
Install the PostHog React Native library and its dependencies:
PostHog AI
Expo
npx expo install posthog-react-native expo-file-system expo-application expo-device expo-localizationyarn
yarn add posthog-react-native @react-native-async-storage/async-storage react-native-device-info react-native-localize
# for iOS
cd ios && pod installnpm
npm i -s posthog-react-native @react-native-async-storage/async-storage react-native-device-info react-native-localize
# for iOS
cd ios && pod install2. 2
Configure PostHog
Required
PostHog is most easily used via the PostHogProvider component. Wrap your app with the provider:
App.tsx
PostHog AI
import { PostHogProvider } from 'posthog-react-native'
export function MyApp() {
return (
<PostHogProvider
apiKey="<ph_project_token>"
options={{
host: "https://us.i.posthog.com",
}}
>
<RestOfApp />
</PostHogProvider>
)
}3. 3
Send events
Recommended
Once installed, PostHog will automatically start capturing events. You can also manually send events using the usePostHog hook:
Component.tsx
PostHog AI
import { usePostHog } from 'posthog-react-native'
function MyComponent() {
const posthog = usePostHog()
const handlePress = () => {
posthog.capture('button_pressed', {
button_name: 'signup'
})
}
return <Button onPress={handlePress} title="Sign Up" />
}4. 4
Use feature flags
Required
PostHog provides hooks to make it easy to use feature flags in your React Native app. Use useFeatureFlagEnabled for boolean flags:
Component.tsx
PostHog AI
import { usePostHog } from 'posthog-react-native'
function MyComponent() {
const posthog = usePostHog()
const isMyFlagEnabled = posthog.isFeatureEnabled('flag-key')
if (isMyFlagEnabled) {
// Do something differently for this user
// Optional: fetch the payload
const matchedFlagPayload = posthog.getFeatureFlagResult('flag-key')?.payload
}
return <View>...</View>
}Multivariate flags
For multivariate flags, use getFeatureFlag:
Component.tsx
PostHog AI
import { usePostHog } from 'posthog-react-native'
function MyComponent() {
const posthog = usePostHog()
const enabledVariant = posthog.getFeatureFlag('flag-key')
if (enabledVariant === 'variant-key') { // replace 'variant-key' with the key of your variant
// Do something differently for this user
// Optional: fetch the payload
const matchedFlagPayload = posthog.getFeatureFlagResult('flag-key')?.payload
}
return <View>...</View>
}5. 5
Running experiments
Optional
Experiments run on top of our feature flags. Once you've implemented the flag in your code, you run an experiment by creating a new experiment in the PostHog dashboard.
6. 6
Next steps
Recommended
Now that you're evaluating flags, continue with the resources below to learn what else Feature Flags enables within the PostHog platform.
| Resource | Description |
|---|---|
| Creating a feature flag | How to create a feature flag in PostHog |
| Adding feature flag code | How to check flags in your code for all platforms |
| Framework-specific guides | Setup guides for React Native, Next.js, Flutter, and other frameworks |
| How to do a phased rollout | Gradually roll out features to minimize risk |
| More tutorials | Other real-world examples and use cases |
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
Related skills
FAQ
What does instrument-feature-flags do?
instrument-feature-flags: A skill for development.
When should I use instrument-feature-flags?
When you need to use instrument-feature-flags for development tasks, or when instrument-feature-flags: a skill for development.
What are the main capabilities?
instrument-feature-flags.