
Instrument Product Analytics
- 311 installs
- 70 repo stars
- Updated August 4, 2026
- posthog/ai-plugin
instrument-product-analytics: A skill for development.
About
instrument-product-analytics: A skill for development. This provides functionality for development workflows.
- instrument-product-analytics
Instrument Product Analytics by the numbers
- 311 all-time installs (skills.sh)
- +22 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,315 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-product-analyticsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 311 |
|---|---|
| repo stars | ★ 70 |
| Last updated | August 4, 2026 |
| Repository | posthog/ai-plugin ↗ |
How do I use instrument-product-analytics for development tasks?
Use instrument-product-analytics for development tasks
Who is it for?
Best when you're working on backend & apis and need structured help with instrument product analytics.
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-product-analytics for development tasks, or when instrument-product-analytics: a skill for development.
What you get
Structured output aligned to instrument-product-analytics: instrument-product-analytics.
Files
Android - Docs
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 mobile app.
Installation
The best way to install the PostHog Android library is with a build system like Gradle. This ensures you can easily upgrade to the latest versions.
All you need to do is add the posthog-android module to your App's build.gradle or build.gradle.kts:
PostHog AI
app/build.gradle
dependencies {
implementation 'com.posthog:posthog-android:3.+'
}app/build.gradle.kts
dependencies {
implementation("com.posthog:posthog-android:3.+")
}Configuration
The best place to initialize the client is in your Application subclass.
Kotlin
PostHog AI
import android.app.Application
import com.posthog.android.PostHogAndroid
import com.posthog.android.PostHogAndroidConfig
class SampleApp : Application() {
companion object {
const val POSTHOG_API_KEY = "<ph_project_token>"
// usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
const val POSTHOG_HOST = "https://us.i.posthog.com"
}
override fun onCreate() {
super.onCreate()
val config = PostHogAndroidConfig(
apiKey = POSTHOG_API_KEY,
host = POSTHOG_HOST
)
PostHogAndroid.setup(this, config)
}
}Capturing events
You can send custom events using capture:
Kotlin
PostHog AI
import com.posthog.PostHog
PostHog.capture(event = "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:
Kotlin
PostHog AI
import com.posthog.PostHog
PostHog.capture(
event = "user_signed_up",
properties = mapOf(
"login_type" to "email",
"is_free_trial" to true
)
)Autocapture
PostHog autocapture automatically tracks the following events for you:
- Application Opened - when the app is opened from a closed state or when the app comes to the foreground. (e.g. from the app switcher)
- Deep Link Opened - when the app is opened from a deep link.
- Application Backgrounded - when the app is sent to the background by the user.
- Application Installed - when the app is installed.
- Application Updated - when the app is updated.
- $screen - when the user navigates. (if using
android.app.Activity) - $exception - when uncaught exception autocapture is enabled. To use this, enable Android error tracking and exception autocapture in the SDK config.
Capturing screen views
With `captureScreenViews = true`, PostHog will try to record all screen changes automatically.
The screenTitle will be the `<activity>`'s android:label, if not set it'll fallback to the `<application>`'s android:label or the `<activity>`'s android:name.
XML
PostHog AI
<activity
android:name="com.example.app.ChildActivity"
android:label="@string/title_child_activity"
...
</activity>If you want to manually send a new screen capture event, use the screen function.
This function requires a screenTitle. You may also pass in an optional properties object.
Kotlin
PostHog AI
import com.posthog.PostHog
PostHog.screen(
screenTitle = "Dashboard",
properties = mapOf(
"background" to "blue",
"hero" to "superhog"
)
)Identifying users
We highly recommend reading our section on Identifying users to better understand how to correctly use this method.
Using identify, you can associate events with specific users. This enables you to gain full insights as to how they're using your product across different sessions, devices, and platforms.
An identify call has the following arguments:
- distinctId: Required. A unique identifier for your user. Typically either their email or database ID.
- userProperties: Optional. A dictionary with key:value pairs to set the person properties
- userPropertiesSetOnce: Optional. Similar to
userProperties. See the difference between `userProperties` and `userPropertiesSetOnce`
Kotlin
PostHog AI
import com.posthog.PostHog
PostHog.identify(
distinctId = distinctID,
userProperties = mapOf(
"name" to "Max Hedgehog",
"email" to "max@hedgehogmail.com"
),
userPropertiesSetOnce = mapOf(
"date_of_first_log_in" to "2024-03-01"
),
)You should call identify as soon as you're able to. Typically, this is after your user logs in. This ensures that events sent during your user's sessions are correctly associated with them.
When you call identify, all previously tracked anonymous events will be linked to the user.
Get the current user's distinct ID
You may find it helpful to get the current user's distinct ID. For example, to check whether you've already called identify for a user or not.
To do this, call distinctId(). This returns either the ID automatically generated by PostHog or the ID that has been passed by a call to identify().
Tracing headers
Use tracingHeaders to connect Android network requests to backend events, errors, and LLM traces captured by a server-side PostHog SDK. Tracing headers are added by the PostHogOkHttpInterceptor, so install the interceptor on each OkHttpClient whose requests should include PostHog context.
Kotlin
PostHog AI
import com.posthog.PostHogOkHttpInterceptor
import com.posthog.android.PostHogAndroid
import com.posthog.android.PostHogAndroidConfig
import okhttp3.OkHttpClient
val config = PostHogAndroidConfig(
apiKey = POSTHOG_API_KEY,
host = POSTHOG_HOST,
).apply {
tracingHeaders = listOf("api.example.com")
}
PostHogAndroid.setup(this, config)
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(PostHogOkHttpInterceptor())
.build()Hostnames are matched exactly and should not include protocols, paths, ports, or wildcard subdomains. Matching OkHttp requests include X-POSTHOG-DISTINCT-ID and X-POSTHOG-SESSION-ID when those values are available.
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.
Kotlin
PostHog AI
/**
* Create an alias for the current user.
*/
PostHog.alias("distinct_id")We strongly recommend reading our docs on alias to best understand how to correctly use this method.
Anonymous and identified events
PostHog captures two types of events: **anonymous** and **identified**
Identified events enable you to attribute events to specific users, and attach person properties. They're best suited for logged-in users.
Scenarios where you want to capture identified events are:
- Tracking logged-in users in B2B and B2C SaaS apps
- Doing user segmented product analysis
- Growth and marketing teams wanting to analyze the complete conversion lifecycle
Anonymous events are events without individually identifiable data. They're best suited for web analytics or apps where users aren't logged in.
Scenarios where you want to capture anonymous events are:
- Tracking a marketing website
- Content-focused sites
- B2C apps where users don't sign up or log in
Under the hood, the key difference between identified and anonymous events is that for identified events we create a person profile for the user, whereas for anonymous events we do not.
Important: Due to the reduced cost of processing them, anonymous events can be up to 4x cheaper than identified ones, so we recommended you only capture identified events when needed.
How to capture anonymous events
The Android SDK captures anonymous events by default. However, this may change depending on your personProfiles config when initializing PostHog:
1. personProfiles = PersonProfiles.IDENTIFIED_ONLY (recommended) (default) - Anonymous events are captured by default. PostHog only captures identified events for users where person profiles have already been created.
2. personProfiles = PersonProfiles.ALWAYS - Capture identified events for all events.
3. personProfiles = PersonProfiles.NEVER - Capture anonymous events for all events.
For example:
Kotlin
PostHog AI
val config = PostHogAndroidConfig(
apiKey = POSTHOG_API_KEY,
host = POSTHOG_HOST,
).apply {
personProfiles = PersonProfiles.IDENTIFIED_ONLY
}How to capture identified events
If you've set the `personProfiles` config to IDENTIFIED_ONLY (the default option), anonymous events are captured by default. Then, to capture identified events, call any of the following functions:
When you call any of these functions, it creates a person profile for the user. Once this profile is created, all subsequent events for this user will be captured as identified events.
Alternatively, you can set personProfiles to ALWAYS to capture identified events by default.
Setting person properties
To set properties on your users via an event, you can leverage the event properties userProperties and userPropertiesSetOnce.
When capturing an event, you can pass a property called userProperties as an event property, and specify its value to be an object with properties to be set on the user that will be associated with the user who triggered the event.
Kotlin
PostHog AI
import com.posthog.PostHog
PostHog.capture(
event = "button_b_clicked",
properties = mapOf("color" to "blue"),
userProperties = mapOf(
"string" to "value1",
"integer" to 2
)
)userPropertiesSetOnce works just like userProperties, except that it will only set the property if the user doesn't already have that property set.
Kotlin
PostHog AI
import com.posthog.PostHog
PostHog.capture(
event = "button_b_clicked",
properties = mapOf("color" to "blue"),
userPropertiesSetOnce = mapOf(
"string" to "value1",
"integer" to 2
)
)Super Properties
Super Properties are properties associated with events that are set once and then sent with every capture call, be it a $screen, or anything else.
They are set using PostHog.register, which takes a key and value, and they persist across sessions.
For example, take a look at the following call:
Kotlin
PostHog AI
import com.posthog.PostHog
PostHog.register("team_id", 22)The call above ensures that every event sent by the user will include "team_id": 22. This way, if you filtered events by property using team_id = 22, it would display all events captured on that user after the PostHog.register call, since they all include the specified Super Property.
However, please note that this does not store properties against the User, only against their events. To store properties against the User object, you should use PostHog.identify. More information on this can be found on the Sending User Information section.
Removing stored Super Properties
Super Properties are persisted across sessions so you have to explicitly remove them if they are no longer relevant. In order to stop sending a Super Property with events, you can use PostHog.unregister, like so:
Kotlin
PostHog AI
import com.posthog.PostHog
PostHog.unregister("team_id")This will remove the Super Property and subsequent events will not include it.
If you are doing this as part of a user logging out you can instead simply use PostHog.reset which takes care of clearing all stored Super Properties and more.
Opt out of data capture
You can completely opt-out users from data capture. To do this, there are two options:
1. Opt users out by default by setting optOut to true in your PostHog config:
Kotlin
PostHog AI
val config = PostHogAndroidConfig(
apiKey = <ph_project_token>,
host = https://us.i.posthog.com
)
config.optOut = true
PostHogAndroid.setup(this, config)2. Opt users out on a per-person basis by calling optOut():
Kotlin
PostHog AI
PostHog.optOut()Similarly, you can opt users in:
Kotlin
PostHog AI
PostHog.optIn()To check if a user is opted out:
Kotlin
PostHog AI
PostHog.isOptOut()Flush
You can configure how many events queue before flushing with flushAt. Setting this to 1 will send events immediately and will use more battery. The default is 20.
You can also configure the flush interval with flushIntervalSeconds (default 30), after which queued events are sent regardless of how many have been gathered:
Kotlin
PostHog AI
import com.posthog.android.PostHogAndroidConfig
val config = PostHogAndroidConfig(apiKey = POSTHOG_API_KEY, host = POSTHOG_HOST).apply {
flushAt = 20
flushIntervalSeconds = 30
}You can also manually flush the queue to start sending events immediately instead of waiting for the next batch:
Kotlin
PostHog AI
import com.posthog.PostHog
PostHog.flush()Flushing is best-effort and asynchronous – it starts sending queued events in the background but doesn't wait for the request to finish, so it isn't a delivery guarantee.
Reset after logout
To reset the user's ID and anonymous ID, call reset. Usually you would do this right after the user logs out.
Kotlin
PostHog AI
import com.posthog.PostHog
PostHog.reset()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.
Boolean feature flags
Kotlin
PostHog AI
import com.posthog.PostHog
if (PostHog.isFeatureEnabled("flag-key")) {
// Do something differently for this user
// Optional: fetch the payload
val matchedFlagPayload = PostHog.getFeatureFlagResult("flag-key")?.payload
}Multivariate feature flags
Kotlin
PostHog AI
import com.posthog.PostHog
if (PostHog.getFeatureFlag("flag-key") == "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
}Feature flag values and payloads together
If you need both the flag value and payload, use getFeatureFlagResult so both values come from the same evaluation result.
Kotlin
PostHog AI
import com.posthog.PostHog
val result = PostHog.getFeatureFlagResult("flag-key")
if (result?.value == "variant-key") {
val payload = result.payload
// Do something with the variant and payload
}You can also inspect all currently loaded feature flag results with PostHog.getAllFeatureFlags().
Ensuring flags are loaded before usage
Every time a user opens the app, we send a request in the background to fetch the feature flags that apply to that user. We store those flags in the storage.
This means that for most screens, the feature flags are available immediately – except for the first time a user visits.
To handle this, you can use the onFeatureFlags callback to wait for the feature flag request to finish:
Kotlin
PostHog AI
import com.posthog.PostHog
import com.posthog.android.PostHogAndroidConfig
import com.posthog.PostHogOnFeatureFlags
// During SDK initialization
val config = PostHogAndroidConfig(apiKey = "<ph_project_token>").apply {
onFeatureFlags = PostHogOnFeatureFlags {
if (PostHog.isFeatureEnabled("flag-key")) {
// do something
}
}
}
// And/or after the SDK is initialized
PostHog.reloadFeatureFlags {
if (PostHog.isFeatureEnabled("flag-key")) {
// do something
}
}Reloading feature flags
Feature flag values are cached. If something has changed with your user and you'd like to refetch their flag values, call:
Kotlin
PostHog AI
import com.posthog.PostHog
PostHog.reloadFeatureFlags()Tracking feature usage
To track when someone sees or interacts with a feature, use captureFeatureView and captureFeatureInteraction.
Kotlin
PostHog AI
import com.posthog.PostHog
PostHog.captureFeatureView("flag-key", flagVariant = "variant-key")
PostHog.captureFeatureInteraction("flag-key", flagVariant = "variant-key")Experiments (A/B tests)
Since experiments use feature flags, the code for running an experiment is very similar to the feature flags code:
Kotlin
PostHog AI
import com.posthog.PostHog
if (PostHog.getFeatureFlag("experiment-feature-flag-key") == "variant-name") {
// do something
}It's also possible to run experiments without using feature flags.
Group analytics
Group analytics allows you to associate the events for that person's session 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 the pricing page.
- Associate the events for this session with a group
Kotlin
PostHog AI
import com.posthog.PostHog
// organization is the group type, company_id_in_your_db is the group ID
PostHog.group(
type = "company",
key = "company_id_in_your_db"
)- Associate the events for this session with a group AND update the properties of that group
Kotlin
PostHog AI
import com.posthog.PostHog
PostHog.group(
type = "company",
key = "company_id_in_your_db",
groupProperties = mapOf("name" to "Awesome Inc.")
)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.
Error tracking
To set up error tracking in your project, follow the Android installation guide.
Logs
To set up logs in your Android app, follow the Android logs installation guide. The SDK exposes PostHog.logger.{trace,debug,info,warn,error,fatal} for sending structured records to PostHog Logs, with batching, offline persistence, and a rate cap built in.
Minimum version: com.posthog:posthog-android@3.46.0 or later.Session replay
To set up session replay in your project, all you need to do is install the Android SDK, enable "Record user sessions" in your project settings and enable the sessionReplay option.
Surveys
To set up surveys, follow the additional installation instructions for Android. Surveys launched with popover presentation are automatically shown to users matching the display conditions you set up.
Offline behavior
The PostHog Android SDK will continue to capture events when the device is offline. The events are stored in a queue in the device's file storage and are flushed when the device is online.
- The queue has a maximum size defined by
maxQueueSizein the configuration. - When the queue is full, the oldest event is deleted first.
- The queue is flushed when the app is restarted and the device is online.
- When you call `flush()` while the device is offline, it aborts early and the events are not flushed.
Debug mode
If you're not seeing the expected events being captured, the feature flags being evaluated, surveys being shown, or session replay/error tracking behavior, you can enable debug mode to see what's happening.
You can enable debug mode by setting the debug option to true in the PostHogAndroidConfig object. This will enable verbose logs about the inner workings of the SDK.
Kotlin
PostHog AI
val config = PostHogAndroidConfig(apiKey = POSTHOG_API_KEY, host = POSTHOG_HOST).apply {
debug = true
// ... other config options
}All configuration options
When creating the PostHog client, pass a PostHogAndroidConfig. It inherits the core PostHogConfig options and adds Android-specific options.
Kotlin
PostHog AI
import com.posthog.PersonProfiles
import com.posthog.android.PostHogAndroidConfig
val config = PostHogAndroidConfig(
apiKey = POSTHOG_API_KEY,
host = POSTHOG_HOST
).apply {
captureApplicationLifecycleEvents = true
captureScreenViews = true
captureDeepLinks = true
flushAt = 20
maxQueueSize = 1000
maxBatchSize = 50
maxRetries = 3
flushIntervalSeconds = 30
debug = false
optOut = false
sendFeatureFlagEvent = true
featureFlagCalledCacheSize = 1000
preloadFeatureFlags = true
evaluationContexts = listOf("production", "android", "mobile")
setDefaultPersonProperties = true
personProfiles = PersonProfiles.IDENTIFIED_ONLY
reuseAnonymousId = false
sessionReplay = false
errorTrackingConfig.autoCapture = false
}Android-specific options
| Option | Default | Description |
|---|---|---|
| captureApplicationLifecycleEvents | true | Captures Application Installed, Application Updated, Application Opened, and Application Backgrounded. |
| captureScreenViews | true | Captures $screen for foreground android.app.Activity screens. |
| captureDeepLinks | true | Captures Deep Link Opened with URL/query/referrer properties. |
Core options
| Option | Default | Description |
|---|---|---|
| debug | false | Enables verbose SDK logs in Logcat. You can also call PostHog.debug(true). |
| optOut | false | Prevents data capture when enabled. You can also call PostHog.optOut() and PostHog.optIn(). |
| flushAt | 20 | Number of queued events that triggers a flush. |
| maxQueueSize | 1000 | Maximum number of events kept across memory and disk before FIFO eviction. |
| maxBatchSize | 50 | Maximum number of events sent in one batch request. |
| maxRetries | 3 | Maximum retry attempts for failed requests. |
| flushIntervalSeconds | 30 | Maximum delay before queued data is flushed. |
| encryption | null | Optional PostHogEncryption implementation for encrypting persisted queued events. |
| proxy | null | Optional java.net.Proxy for PostHog API requests. |
| getAnonymousId | generated UUID | Optional hook to customize anonymous ID generation. |
| reuseAnonymousId | false | Reuses one anonymous ID across user changes on the same device. |
| personProfiles | PersonProfiles.IDENTIFIED_ONLY | Controls when person profiles are processed: IDENTIFIED_ONLY, ALWAYS, or NEVER. |
| setDefaultPersonProperties | true | Includes default person properties for person profile updates. |
| releaseIdentifier | app/version fallback | Release identifier used by error tracking and uploaded ProGuard/R8 mappings. The Android Gradle plugin can inject this automatically. |
| tracingHeaders | null | Exact hostnames that should receive PostHog tracing headers when using PostHogOkHttpInterceptor. |
Feature flag options
| Option | Default | Description |
|---|---|---|
| sendFeatureFlagEvent | true | Sends $feature_flag_called when a feature flag is evaluated. |
| featureFlagCalledCacheSize | 1000 | Number of feature flag calls cached for deduplicating $feature_flag_called events. |
| preloadFeatureFlags | true | Fetches feature flags automatically during setup. |
| evaluationContexts | null | Context tags that constrain which feature flags are evaluated. Available in version 3.25.0+. |
| onFeatureFlags | null | Callback invoked when feature flags are loaded. |
Product configuration objects
| Option | Default | Description |
|---|---|---|
| sessionReplay | false | Enables session replay when project settings also allow recording. |
| sessionReplayConfig | PostHogSessionReplayConfig() | Configures masking, screenshots, Logcat capture, sampling, and custom drawable conversion. |
| logs | PostHogLogsConfig() | Configures Android logs. |
| errorTrackingConfig | PostHogErrorTrackingConfig() | Configures error tracking. autoCapture defaults to false; set it to true to autocapture uncaught exceptions when project settings also enable error tracking. |
| surveys | false | Internal/experimental native Android survey support. Native Android survey UI is not fully supported or documented yet. |
| surveysConfig | PostHogSurveysConfig() | Internal/experimental survey display delegate configuration, primarily for hybrid SDKs. |
Event filtering with beforeSend
Use addBeforeSend to redact, modify, or drop events before they are queued. Return null to drop an event.
Kotlin
PostHog AI
config.addBeforeSend { event ->
event.properties?.remove("password")
if (event.event == "internal_debug_event") {
null
} else {
event
}
}FAQ
What Android API level is required?
The Android SDK supports Android API 23 and newer.
Do I need to declare permissions in the AndroidManifest.xml?
Usually, no. The SDK declares android.permission.INTERNET and android.permission.ACCESS_NETWORK_STATE, and Android's manifest merger adds them to your app. The SDK does not declare or require an Android Service.
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
Angular - Docs
PostHog makes it easy to get data about traffic and usage of your Angular 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 Angular app using the JavaScript Web SDK.
Installation
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-jsInitialize the PostHog client
Generate environment files for your project with ng g environments. Configure the following environment variables:
-
posthogKey: Your project token from your project settings. -
posthogHost: Your project's client API host. Usuallyhttps://us.i.posthog.comfor US-based projects andhttps://eu.i.posthog.comfor EU-based projects.
Angular v17+
For Angular v17 and above, you can set up PostHog as a singleton service. To do this, start by creating and injecting a PosthogService instance.
Create a service by running ng g service services/posthog. The service should look like this:
posthog.service.ts
PostHog AI
// src/app/services/posthog.service.ts
import { Injectable, NgZone } from "@angular/core";
import posthog from "posthog-js";
import { environment } from "../../environments/environment";
@Injectable({ providedIn: "root" })
export class PosthogService {
constructor(
private ngZone: NgZone,
) {
this.initPostHog();
}
private initPostHog() {
this.ngZone.runOutsideAngular(() => {
posthog.init(environment.posthogKey, {
api_host: environment.posthogHost,
defaults: '2026-01-30',
});
});
}
}The service is initialized outside of the Angular zone to reduce change detection cycles. This is important to avoid performance issues with session recording.
Then, inject the service in your app's root component app.component.ts. This will make sure PostHog is initialized before any other component is rendered.
app.component.ts
PostHog AI
// src/app/app.component.ts
import { Component } from "@angular/core";
import { RouterOutlet } from "@angular/router";
import { PosthogService } from "./services/posthog.service";
@Component({
selector: "app-root",
styleUrls: ["./app.component.scss"],
template: `
<router-outlet />`,
imports: [RouterOutlet],
})
export class AppComponent {
title = "angular-app";
constructor(posthogService: PosthogService) {}
}Angular v16 and below
In your src/main.ts, initialize PostHog using your project token and instance address. You can find both in your project settings.
main.ts
PostHog AI
// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
import { environment } from "./environments/environment";
import posthog from 'posthog-js'
posthog.init(environment.posthogKey, {
api_host: environment.posthogHost,
defaults: '2026-01-30'
})
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));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.
Note: If you're using Typescript, you might have some trouble getting your types to compile because we depend onrrwebbut don't ship all of their types. To accommodate that, you'll need to add@rrweb/types@2.0.0-alpha.17andrrweb-snapshot@2.0.0-alpha.17as a dependency if you want your Angular compiler to typecheck correctly.
>
Given the nature of this library, you might need to completely clear your .npm cache to get this to work as expected. Make sure your clear your CI's cache as well.>
In the rare case the versions above get out-of-date, you can check our JavaScript SDK's `package.json` to understand what's the exact version you need to depend on.
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).
Tracking pageviews
PostHog automatically tracks your pageviews by hooking up to the browser's navigator API as long as you initialize PostHog with the defaults config option set after 2026-01-30.
Capture custom events
To capture custom events, import posthog and call posthog.capture(). Below is an example of how to do this in a component:
app.component.ts
PostHog AI
import { Component } from '@angular/core';
import posthog from 'posthog-js'
@Component({
// existing component code
})
export class AppComponent {
handleClick() {
posthog.capture(
'home_button_clicked',
)
}
}Session replay
Session replay uses change detection to record the DOM. This can clash with Angular's change detection.
The recorder tool attempts to detect when an Angular zone is present and avoid the clash but might not always succeed.
- If you followed the installation instructions for Angular v17 and above, you don't need to do anything.
- If you followed the installation instructions for Angular v16 and below and you see performance impact from recording in an Angular project, ensure that you use `ngZone.runOutsideAngular`.
posthog.service.ts
PostHog AI
import { Injectable } from '@angular/core';
import posthog from 'posthog-js'
@Injectable({ providedIn: 'root' })
export class PostHogSessionRecordingService {
constructor(private ngZone: NgZone) {}
initPostHog() {
this.ngZone.runOutsideAngular(() => {
posthog.init(
/* your config */
)
})
}
}Angular with SSR
To use PostHog with Angular server-side rendering (SSR), you need to:
1. Update the PostHog web JS client to only initialize on the client-side. 2. Initialize PostHog Node on the server-side.
1\. Update the PostHog web JS client
Update your posthog.service.ts to restrict the initialization of the PostHog web JS client to the client-side. The web SDK uses methods that are not available on the server side, so we need to check if we're on the client side before initializing PostHog.
posthog.service.ts
PostHog AI
import { PLATFORM_ID } from "@angular/core";
@Injectable({ providedIn: "root" })
export class PosthogService {
constructor(
private ngZone: NgZone,
@Inject(PLATFORM_ID) private platformId: Object
) {
// Only initialize PostHog in browser environment
if (isPlatformBrowser(this.platformId)) {
this.initPostHog(); //+
}
}
private initPostHog() {
this.ngZone.runOutsideAngular(() => {
posthog.init(environment.posthogKey, {2\. Add server-side initialization
Angular SSR uses a server.ts file to handle requests. We can add any server-side initialization code to this file.
First, install the posthog-node package to run on the server side.
PostHog AI
npm
npm install posthog-node --saveYarn
yarn add posthog-nodepnpm
pnpm add posthog-nodeBun
bun add posthog-nodeThen, add the following code to the server.ts file:
server.ts
PostHog AI
// src/server.ts
import { environment } from './environments/environment';
import { PostHog } from 'posthog-node'
/**
* Extract distinct ID from PostHog cookie
*/
function getDistinctIdFromCookie(cookieHeader: string | undefined): string | null {
if (!cookieHeader) return null;
const cookieMatch = cookieHeader.match(`ph_${environment.posthogKey}_posthog=([^;]+)`);
if (cookieMatch) {
try {
const parsed = JSON.parse(decodeURIComponent(cookieMatch[1]));
return parsed?.distinct_id || null;
} catch (error) {
console.error('Error parsing PostHog cookie:', error);
return null;
}
}
return null;
}
/**
* Handle all other requests by rendering the Angular application.
*/
app.get('**', async (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req;
const distinctId = getDistinctIdFromCookie(headers.cookie);
let isFeatureEnabled = false;
const client = new PostHog(
environment.posthogKey,
{ host: environment.posthogHost }
);
if (distinctId) {
client.capture({
distinctId: distinctId,
event: 'test_ssr_event',
properties: {
message: 'Hello from Angular SSR!'
}
})
isFeatureEnabled = await client.isFeatureEnabled(
'your_feature_flag_key', distinctId) || false;
}
commonEngine
.render({
bootstrap,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: browserDistFolder,
providers: [
{ provide: APP_BASE_HREF, useValue: baseUrl },
{ provide: 'FEATURE_FLAG_ENABLED', useValue: isFeatureEnabled }
],
})
.then((html) => res.send(html))
.catch((err) => next(err));
await client.shutdown()
});This code does the following:
- Extracts the distinct ID from the cookie header. This is set by the web JS client.
- Captures an event on the server side.
- Evaluates a feature flag on the server side. This can be passed as a provider to the Angular application.
- Calls
shutdownon the PostHog Node client to ensure all events are flushed.
Using PostHog in server-side code
Angular SSR does not allow Node.js code to be bundled into client-side components. Even though resolvers and other server-side code can be written along with client-side components, you cannot use PostHog Node in those components.
Next steps
For any technical questions for how to integrate specific PostHog features into Angular (such as feature flags, A/B testing, surveys, etc.), have a look at our JavaScript Web SDK docs.
Alternatively, the following tutorials can help you get started:
- How to set up Angular analytics, feature flags, and more
- How to set up A/B tests in Angular
- How to set up surveys in Angular
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
Astro - Docs
PostHog makes it easy to get data about traffic and usage of your Astro 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 Astro app using the JavaScript Web SDK.
Beta: integration via LLM
Install PostHog for Astro 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
In your src/components folder, create a posthog.astro file:
Terminal
PostHog AI
cd ./src/components
# or 'cd ./src && mkdir components && cd ./components' if your components folder doesnt exist
touch posthog.astroIn this file, add your Web snippet which you can find in your project settings. Be sure to include the is:inline directive to prevent Astro from processing it, or you will get Typescript and build errors that property 'posthog' does not exist on type 'Window & typeof globalThis'.
posthog.astro
PostHog AI
<script is:inline>
!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.crossOrigin="anonymous",p.async=!0,p.src=s.api_host+"/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="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getFeatureFlag getFeatureFlagPayload reloadFeatureFlags group updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures getActiveMatchingSurveys getSurveys getNextSurveyStep onSessionId".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>Using with Astro's view transitions (ClientRouter)
If you've opted in to Astro's <ClientRouter> component for client-side navigation, you'll need to add an initialization guard to prevent PostHog from running multiple times during page transitions.
Update your posthog.astro file to wrap the snippet with a check:
posthog.astro
PostHog AI
---
// src/components/posthog.astro
---
<script is:inline>
if (!window.__posthog_initialized) {
window.__posthog_initialized = true;
!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.crossOrigin="anonymous",p.async=!0,p.src=s.api_host+"/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="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getFeatureFlag getFeatureFlagPayload reloadFeatureFlags group updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures getActiveMatchingSurveys getSurveys getNextSurveyStep onSessionId".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',
capture_pageview: 'history_change'
})
}
</script>Without this guard, ClientRouter's soft navigation can re-execute the inline script during page transitions, causing a stack overflow error. The capture_pageview: 'history_change' option ensures pageviews are tracked automatically as users navigate.
The next step is to a create a Layout where we will use posthog.astro. Create a new file PostHogLayout.astro in your src/layouts folder:
Terminal
PostHog AI
cd .. && cd .. # move back to your base directory if you're still in src/components/posthog.astro
cd ./src/layouts
# or 'cd ./src && mkdir layouts && cd ./layouts' if your layouts folder doesn't exist yet
touch PostHogLayout.astroAdd the following code to PostHogLayout.astro:
PostHogLayout.astro
PostHog AI
---
import PostHog from '../components/posthog.astro'
---
<head>
<PostHog />
</head>Lastly, update index.astro to wrap your existing app components with the new Layout:
index.astro
PostHog AI
---
import PostHogLayout from '../layouts/PostHogLayout.astro';
---
<PostHogLayout>
<!-- your existing app components -->
</PostHogLayout>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).
Next steps
For any technical questions for how to integrate specific PostHog features into Astro (such as analytics, feature flags, A/B testing, surveys, etc.), have a look at our JavaScript Web SDK docs.
Alternatively, the following tutorials can help you get started:
- How to set up Astro analytics, feature flags, and more
- How to set up A/B tests in Astro
- How to set up surveys in Astro
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
iOS SDK configuration - Docs
Autocapture configuration
You can enable or disable autocapture through the PostHogConfig object.
Tracing headers
Use tracingHeaders to connect iOS network requests to backend events, errors, and LLM traces captured by a server-side PostHog SDK:
Swift
PostHog AI
let configuration = PostHogConfig(projectToken: "<ph_project_token>", host: "https://us.i.posthog.com")
configuration.tracingHeaders = ["api.example.com"]
PostHogSDK.shared.setup(configuration)Hostnames are matched exactly and should not include protocols, paths, ports, or wildcard subdomains. Matching URLSession requests include X-POSTHOG-DISTINCT-ID and X-POSTHOG-SESSION-ID when those values are available.
Tracing headers require method swizzling, so configuration.enableSwizzling must remain true.
Flush configuration
The iOS SDK 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 mobile app.
You can configure how many events queue before flushing with flushAt. Setting this to 1 will send events immediately and will use more battery. The default is 20.
You can also configure the flush interval with flushIntervalSeconds (default 30), after which queued events are sent regardless of how many have been gathered:
Swift
PostHog AI
configuration.flushAt = 1
configuration.flushIntervalSeconds = 30You can also manually flush the queue to start sending events immediately instead of waiting for the next batch:
Swift
PostHog AI
PostHogSDK.shared.capture("logged_out")
PostHogSDK.shared.flush()Flushing is best-effort and asynchronous – it starts sending queued events in the background but doesn't wait for the request to finish, so it isn't a delivery guarantee.
Amending, dropping or sampling events
Since version 3.28.0, you can provide a BeforeSendBlock function when initializing the SDK to amend, drop or sample events before they are sent to PostHog.
⚠️ Note: This replaces the deprecatedpropertiesSanitizeroption and provides more flexibility in modifying events. You can achieve the same functionality aspropertiesSanitizerby using aBeforeSendBlockthat mutates the event's properties in place.
🚨 Warning: Amending and sampling events is advanced functionality that requires careful implementation. Core PostHog features may require 100% of unmodified events to function properly. We recommend only modifying or sampling your own custom events if possible, and preserving all PostHog internal events in their original form.
Redacting information in events
BeforeSendBlock gives you one place to edit or redact information before it is sent to PostHog. For example:
Redact URLs in event properties
Swift
PostHog AI
let config = PostHogConfig(projectToken: "<ph_project_token>", host: "<ph_api_client_host>")
config.setBeforeSend { event in
// Redact URLs
if let url = event.properties["url"] as? String {
event.properties["url"] = url.map { _ in "*" }.joined()
}
return event
}Redact sensitive information from event properties
Swift
PostHog AI
let config = PostHogConfig(projectToken: "<ph_project_token>", host: "<ph_api_client_host>")
config.setBeforeSend { event in
// Redact sensitive information
if let email = event.properties["email"] as? String {
event.properties["email"] = email.map { _ in "*" }.joined()
}
return event
}Drop events by event name
Swift
PostHog AI
let config = PostHogConfig(projectToken: "<ph_project_token>", host: "<ph_api_client_host>")
config.setBeforeSend { event in
// Drop all events named "Stale Event"
if event.event == "Stale Event" {
return nil
}
return event
}Sampling events
Sampling lets you choose to send only a percentage of events to PostHog. It is a good way to control your costs without having to completely turn off features of the SDK.
Sample events by event name
Swift
PostHog AI
let config = PostHogConfig(projectToken: "<ph_project_token>", host: "<ph_api_client_host>")
config.setBeforeSend { event in
// Sample 10% of Sampled Event events
if event.event == "Sampled Event" {
if Double.random(in: 0...1) < 0.1 {
event.properties["$sample_type"] = ["sampleByEvent"]
event.properties["$sample_threshold"] = 0.1
event.properties["$sampled_events"] = ["Sampled Event"]
return event
}
return nil
}
return event
}Chaining multiple BeforeSendBlocks
You can provide an array of BeforeSendBlock functions to be called one after the other:
Swift
PostHog AI
let config = PostHogConfig(projectToken: "<ph_project_token>", host: "<ph_api_client_host>")
config.setBeforeSend(
// First block: Drop all events named "Stale Event"
{ event in
if event.event == "Stale Event" {
return nil
}
return event
},
// Second block: Redact sensitive information
{ event in
if let email = event.properties["email"] as? String {
event.properties["email"] = email.map { _ in "*" }.joined()
}
return event
}
)Note: When chaining beforeSend blocks, order is important. The first block is executed first and the mutated event is passed along to the second block, and so on. If at any point in the chain the event is dropped, any subsequent blocks will not be executed.
Setting up app groups
1. Configure App Groups: Set up an App Group in Xcode for your main app and extension targets 2. Configure PostHog: Use the same App Group identifier in all targets:
Swift
PostHog AI
let config = PostHogConfig(projectToken: "<ph_project_token>", host: "<ph_api_client_host>")
config.appGroupIdentifier = "group.com.yourcompany.yourapp"
PostHogSDK.shared.setup(config)Method swizzling
Method swizzling is a technique that enables the SDK to intercept and modify method calls at runtime to provide advanced features like screen view tracking, element interactions, session replay, surveys, and more.
Method swizzling is enabled by default, but can be disabled by setting the relevant config option to false in the PostHogConfig object:
| Feature | Description | Config option |
|---|---|---|
| Screen view tracking | Automatically captures when view controllers are presented | config.captureScreenViews |
| Element interactions | Automatically tracks user interactions with UI elements | config.captureElementInteractions |
| Rage clicks | Automatically captures $rageclick events for rapid repeated taps in the same area (iOS/macCatalyst, UIKit) | config.rageClickConfig.enabled |
| Session replay | Records user sessions | config.sessionReplay |
| Surveys | Displays surveys at appropriate times | config.surveys |
| Advanced metrics tracking | Provides more precise session ID calculation and rotation by detecting user activity and idleness | N/A |
Disabling all method swizzling
Since version 3.34.0, you can opt out of all swizzling using the enableSwizzling configuration option. When you disable swizzling, the SDK disables the features listed above.
Swift
PostHog AI
let config = PostHogConfig(projectToken: "<ph_project_token>", host: "<ph_api_client_host>")
config.enableSwizzling = false
PostHogSDK.shared.setup(config)Note: When method swizzling is disabled, features that depend on it will not work even if they are individually enabled in the config. For example, if you setconfig.sessionReplay = trueandconfig.enableSwizzling = false, session replay will not be enabled.
Session metrics management
Method swizzling is particularly important for accurate session metrics tracking. With swizzling enabled, the SDK can better detect user activity and idle times to provide a better session rotation.
With swizzling disabled, the SDK only uses application open/backgrounded events to detect user activity, which can lead to a sub-optimal session calculation.
Custom keyboard extensions
Custom keyboard extensions have stricter security rules than other extension types. To use PostHog in a custom keyboard, the keyboard must have Open Access permission enabled. This permission is required for network requests and write access to shared containers.
Users must explicitly grant Open Access in Settings > General > Keyboard > Keyboards > \[Your Keyboard\] > Allow Full Access.
All configuration options
The `PostHogConfig` object contains several other settings you can toggle:
| Attribute | Description |
|---|---|
| flushAtType: IntegerDefault: 20 (5 on tvOS) | The number of queued events that the posthog client should flush at. Setting this to 1 will not queue any events and will use more battery. |
| flushIntervalSecondsType: IntegerDefault: 30 | The amount of time to wait before each tick of the flush timer. Smaller values will make events delivered in a more real-time manner and also use more battery. A value smaller than 10 seconds will seriously degrade overall performance. |
| maxQueueSizeType: IntegerDefault: 1000 (100 on tvOS) | The maximum number of items to queue before starting to drop old ones. This should be a value greater than zero, the behavior is undefined otherwise. |
| maxBatchSizeType: IntegerDefault: 50 | Number of maximum events in a batch call. |
| maxRetriesType: IntegerDefault: 3 | Maximum number of consecutive flush attempts before the entire queue is dropped to avoid infinite retries against a permanently-broken backend (e.g. wrong API key, exhausted quota, deterministic 5xx). Increments on every retriable failure including HTTP 413 cap halving; resets on a successful 2xx response. |
| captureApplicationLifecycleEventsType: BooleanDefault: true | Whether the posthog client should automatically make a capture call for application lifecycle events, such as "Application Installed", "Application Updated" and "Application Opened". |
| captureScreenViewsType: BooleanDefault: true | Whether the posthog client should automatically make a screen call when a view controller is added to a view hierarchy. Because the underlying implementation uses method swizzling, we recommend initializing the posthog client as early as possible (before any screens are displayed), ideally during the Application delegate's applicationDidFinishLaunching method. |
| enableSwizzlingType: BooleanDefault: true | Enable method swizzling for SDK functionality that depends on it. When disabled, functionality that requires swizzling (like autocapture, screen views, session replay, surveys) will not be installed. |
| captureElementInteractionsType: BooleanDefault: false | (UIKit only) Whether the posthog client should automatically make a capture call when the user interacts with an element in a screen. |
| rageClickConfigType: ObjectDefault: .init() | (iOS/macCatalyst, UIKit) Rage click detection configuration. Includes enabled (default true), minimumTapCount (default 3), thresholdPoints (default 30), and timeoutInterval (default 1.0). Works independently of captureElementInteractions. Available in version 3.51.0+. |
| sendFeatureFlagEventType: BooleanDefault: true | Send a $feature_flag_called event when a feature flag is used automatically. |
| preloadFeatureFlagsType: BooleanDefault: true | Preload feature flags automatically. |
| evaluationContextsType: Array of StringsDefault: undefined | Evaluation context tags that constrain which feature flags are evaluated. When set, only flags with matching evaluation context tags (or no evaluation context tags) will be returned. See evaluation contexts documentation for more details. Available in version 3.34.0+. The legacy parameter evaluationEnvironments (version 3.33.0+) is also supported for backward compatibility. |
| debugType: BooleanDefault: false | Logs the SDK messages to the Xcode console. |
| optOutType: BooleanDefault: false | Prevents capturing any data if enabled. |
| getAnonymousIdType: FunctionDefault: undefined | Hook that allows for modification of the default mechanism for generating anonymous id (which as of now is just random UUID v7). |
| dataModeType: EnumDefault: .any | Allows to send your data only if the data mode matches your configuration such as wifi only, cellular only or any. |
| personProfilesType: EnumDefault: .identifiedOnly | Determines the behavior for processing user profiles. |
| setDefaultPersonPropertiesType: BooleanDefault: true | Automatically set common device and app properties (such as $app_version, $os_name, and $device_type) as person properties for feature flag evaluation. See property overrides for more details. |
| sessionReplayType: BooleanDefault: false | Enable Recording of Session Replays. |
| sessionReplayConfigType: ObjectDefault: .init() | Session Replay configuration. See Session Replay installation for more details. |
| tracingHeadersType: Array of StringsDefault: nil | Exact hostnames that should receive PostHog tracing headers when the SDK instruments URLSession requests. |
| errorTrackingConfigType: ObjectDefault: .init() | Error Tracking configuration. See Error Tracking installation for more details. |
| logsType: ObjectDefault: .init() | Structured Logs configuration. See Logs installation for more details. |
| surveysConfigType: ObjectDefault: .init() | Surveys configuration, including custom survey delegates and display language overrides. |
| urlSessionConfigurationType: URLSessionConfigurationDefault: .default | Custom URLSessionConfiguration used by the SDK for PostHog API requests. |
| appGroupIdentifierType: StringDefault: nil | The identifier of the App Group that should be used to store shared analytics data. PostHog will try to get the physical location of the App Group's shared container, otherwise fallback to the default location. |
| reuseAnonymousIdType: BooleanDefault: false | Whether the SDK should reuse the anonymous Id between user changes. When enabled, a single Id will be used for all anonymous users on this device. |
| surveysType: BooleanDefault: true | Enable Surveys. |
| setBeforeSendType: FunctionDefault: undefined | Hook that allows for amending, sampling, or dropping events before they are sent to PostHog. |
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 - Docs
This library provides an Elixir HTTP client for PostHog. See the repository for more information.
Installation
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 automatically capture events from your Plug-based applications including Phoenix.
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.
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
To capture an event, use PostHog.capture/2:
Elixir
PostHog AI
PostHog.capture("user_signed_up", %{distinct_id: "distinct_id_of_the_user"})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:
Elixir
PostHog AI
PostHog.capture("user_signed_up", %{
distinct_id: "distinct_id_of_the_user",
login_type: "email",
is_free_trial: true
})Context
Carrying distinct_id around all the time might not be the most convenient approach, so PostHog lets you store it and other properties in a context.
The context is stored in the Logger metadata and PostHog automatically attaches these properties to any events you capture with PostHog.capture/2, as long as they happen in the same process.
Elixir
PostHog AI
PostHog.set_context(%{distinct_id: "distinct_id_of_the_user"})
PostHog.capture("page_opened")You can also scope the context to a specific event name:
Elixir
PostHog AI
PostHog.set_event_context("sensitive_event", %{"$process_person_profile": false})Batching events
Events are automatically batched and sent to PostHog via a background job.
Special events
PostHog.capture/2 is very powerful and enables you to send events that have special meaning.
In other libraries you'll usually find helpers for these special events, but they must be explicitly sent in Elixir.
For example:
Create alias
Elixir
PostHog AI
PostHog.capture("$create_alias", %{distinct_id: "frontend_id", alias: "backend_id"})Group analytics
Elixir
PostHog AI
PostHog.capture("$groupidentify", %{
distinct_id: "static_string_used_for_all_group_events",
"$group_type": "company",
"$group_key": "company_id_in_your_db"
})Request context
For Phoenix or Plug apps, add PostHog.Integrations.Plug before your router to attach request metadata and PostHog tracing headers to events captured during the request.
lib/my\_app\_web/endpoint.ex
PostHog AI
plug PostHog.Integrations.Plug
plug MyAppWeb.RouterFor plain Plug routers, add it before :match and :dispatch:
Elixir
PostHog AI
defmodule MyRouter do
use Plug.Router
plug PostHog.Integrations.Plug
plug :match
plug :dispatch
# ... routes
endThe plug adds request metadata such as $current_url, $host, $pathname, $request_method, $user_agent, and $ip. It also reads X-PostHog-Distinct-Id and X-PostHog-Session-Id as analytics context so backend events and errors can be linked to frontend users and sessions.
If you're using PostHog JS on the frontend, configure `tracing_headers` for your Phoenix or Plug backend hostname so browser requests include these headers.
Tracing headers are client-controlled analytics context, not authentication or authorization. Pass an authenticated distinct_id explicitly for security-sensitive server-side decisions.
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 Elixir:
Step 1: Evaluate flags once
Call PostHog.FeatureFlags.evaluate_flags/1 once for the user, then read values from the returned snapshot.
Boolean feature flags
Elixir
PostHog AI
{:ok, snapshot} = PostHog.FeatureFlags.evaluate_flags("distinct_id_of_your_user")
if PostHog.FeatureFlags.Evaluations.enabled?(snapshot, "flag-key") do
# Do something differently for this user
# Optional: fetch the payload
payload = PostHog.FeatureFlags.Evaluations.get_flag_payload(snapshot, "flag-key")
endMultivariate feature flags
Elixir
PostHog AI
{:ok, snapshot} = PostHog.FeatureFlags.evaluate_flags("distinct_id_of_your_user")
enabled_variant = PostHog.FeatureFlags.Evaluations.get_flag(snapshot, "flag-key")
if enabled_variant == "variant-key" do
# Do something differently for this user
# Optional: fetch the payload
payload = PostHog.FeatureFlags.Evaluations.get_flag_payload(snapshot, "flag-key")
endPostHog.FeatureFlags.Evaluations.get_flag/2 returns the variant string for multivariate flags, true for enabled boolean flags, false for disabled flags, and nil when the flag wasn't returned by the evaluation.
Note:PostHog.FeatureFlags.check/2,PostHog.FeatureFlags.check!/2,PostHog.FeatureFlags.get_feature_flag_result/2, andPostHog.FeatureFlags.get_feature_flag_result!/2still work during the migration period, but they're deprecated. Preferevaluate_flags/1for 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: Put the evaluated flags snapshot in context
Put the same snapshot object that you used for branching into context. Subsequent captures from the same process attach the exact flag values from that evaluation and don't make another /flags request.
Elixir
PostHog AI
{:ok, snapshot} = PostHog.FeatureFlags.evaluate_flags("distinct_id_of_your_user")
if PostHog.FeatureFlags.Evaluations.enabled?(snapshot, "flag-key") do
# Do something differently for this user
end
PostHog.FeatureFlags.set_in_context(snapshot)
PostHog.capture("event_name", %{distinct_id: "distinct_id_of_your_user"})By default, this attaches every flag in the snapshot using $feature/<flag-key> properties and $active_feature_flags.
To reduce event property bloat, put a filtered snapshot in context:
Elixir
PostHog AI
{:ok, snapshot} = PostHog.FeatureFlags.evaluate_flags("distinct_id_of_your_user")
# Attach only flags accessed with enabled?/2, get_flag/2, or get_flag_payload/2 before this call
PostHog.FeatureFlags.Evaluations.enabled?(snapshot, "flag-key")
PostHog.FeatureFlags.set_in_context(
PostHog.FeatureFlags.Evaluations.only_accessed(snapshot)
)
# Or attach only specific flags
PostHog.FeatureFlags.set_in_context(
PostHog.FeatureFlags.Evaluations.only(snapshot, ["checkout-flow", "new-dashboard"])
)only_accessed/1 is order-dependent. If you call it before accessing any flags with enabled?/2, get_flag/2, or get_flag_payload/2, no feature flag properties are attached.
Method 2: Include the $feature/feature_flag_name property manually
In the event properties, include $feature/feature_flag_name: variant_key:
Elixir
PostHog AI
PostHog.capture("event_name", %{
"$feature/feature-flag-key" => "variant-key",
distinct_id: "distinct_id_of_your_user"
})Evaluating only specific flags
By default, evaluate_flags/1 evaluates every flag for the user. If you only need a few flags, pass flag_keys to request only those flags:
Elixir
PostHog AI
{:ok, snapshot} =
PostHog.FeatureFlags.evaluate_flags(%{
distinct_id: "distinct_id_of_your_user",
flag_keys: ["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 evaluate_flags/1, the SDK sends this event when you call PostHog.FeatureFlags.Evaluations.enabled?/2 or PostHog.FeatureFlags.Evaluations.get_flag/2 for a flag.
PostHog.FeatureFlags.Evaluations.get_flag_payload/2 doesn't send $feature_flag_called events.
Error tracking
Error tracking is enabled by default. It will automatically captures exceptions thrown by the application.
As a matter of fact, since this is built on top of Elixir's Logger module, it automatically captures any Logger.error calls.
You can always disable it by setting enable_error_tracking to false:
Elixir
PostHog AI
config :posthog,
enable_error_tracking: falseAdvanced configuration
By default, PostHog starts its own supervision tree and attaches a logger handler.
In certain cases, you might want to run this supervision tree yourself. You can do this by disabling the default supervisor and adding PostHog.Supervisor to your application tree with its own configuration:
config.exs
PostHog AI
config :posthog, enable: false
config :my_app, :posthog,
api_host: "https://us.i.posthog.com",
api_key: "<ph_project_token>"application.ex
PostHog AI
defmodule MyApp.Application do
use Application
def start(_type, _args) do
posthog_config = Application.fetch_env!(:my_app, :posthog) |> PostHog.Config.validate!()
:logger.add_handler(:posthog, PostHog.Handler, %{config: posthog_config})
children = [
{PostHog.Supervisor, posthog_config}
]
Supervisor.start_link(children, strategy: :one_for_one)
end
endMultiple instances
In even more advanced cases, you might want to interact with more than one PostHog project. In this case, you can run multiple PostHog supervision trees, one of which can be the default one:
config.exs
PostHog AI
config :posthog,
api_host: "https://us.i.posthog.com",
api_key: "<ph_project_token>"
config :my_app, :another_posthog,
api_host: "https://us.i.posthog.com",
api_key: "a_different_project_api_key",
supervisor_name: AnotherPostHogapplication.ex
PostHog AI
defmodule MyApp.Application do
use Application
def start(_type, _args) do
posthog_config = Application.fetch_env!(:my_app, :another_posthog) |> PostHog.Config.validate!()
children = [
{PostHog.Supervisor, posthog_config}
]
Supervisor.start_link(children, strategy: :one_for_one)
end
endThen, each function in the PostHog module accepts an optional first argument with the name of the PostHog supervisor tree that will process the capture:
Elixir
PostHog AI
PostHog.capture(AnotherPostHog, "user_signed_up", %{distinct_id: "user123"})Thanks
The library is maintained by the PostHog team since February 2025. Thanks to nkezhaya for contributing v0.1.0. Thanks to martosaur for contributing v2.0.0.
Community questions
Ask a question
Was this page useful?
HelpfulCould be better
PostHog astro-static Example Project
Repository: https://github.com/PostHog/context-mill Path: example-apps/astro-static
---
README.md
PostHog Astro Static Example
This is an Astro static site (SSG) example demonstrating PostHog integration with product analytics, session replay, and error tracking.
It uses the PostHog web snippet directly and shows how to:
- Initialize PostHog in a static Astro site using a reusable component
- Identify users after login
- Track custom events from pages
- Capture errors via
posthog.captureException() - Reset PostHog state on logout
Features
- Product analytics: Track login and burrito consideration events
- Session replay: Enabled via PostHog snippet configuration
- Error tracking: Manual error capture sent to PostHog
- Simple auth flow: Demo login using localStorage
Getting started
1. Install dependencies
npm install
# or
pnpm install2. Configure environment variables
Create a .env file in the project root:
PUBLIC_POSTHOG_PROJECT_TOKEN=your_posthog_project_token
PUBLIC_POSTHOG_HOST=https://us.i.posthog.comGet your PostHog project token from your project settings in PostHog.
3. Run the development server
npm run dev
# or
pnpm devOpen http://localhost:4321 in your browser.
Project structure
src/
components/
posthog.astro # PostHog snippet with is:inline directive
Header.astro # Navigation + logout, calls posthog.reset()
layouts/
PostHogLayout.astro # Root layout that includes PostHog + Header
lib/
auth.ts # Auth utilities (localStorage-based)
pages/
index.astro # Login form, identifies user + captures 'user_logged_in'
burrito.astro # Burrito consideration demo, captures 'burrito_considered'
profile.astro # Profile + error tracking demo
styles/
global.css # Global stylesKey integration points
PostHog initialization (src/components/posthog.astro)
The PostHog snippet is included as an inline script to prevent Astro from processing it:
<script is:inline>
!function(t,e){...}(document,window.posthog||[]);
posthog.init('<ph_project_token>', {
api_host: 'https://us.i.posthog.com',
defaults: '2026-01-30'
})
</script>The is:inline directive is required to prevent TypeScript errors about window.posthog.
User identification (src/pages/index.astro)
After a successful "login", the app identifies the user and captures a login event:
window.posthog?.identify(username);
window.posthog?.capture("user_logged_in");Identification happens only on login, all further requests will automatically use the same distinct ID.
Event tracking (src/pages/burrito.astro)
The burrito page tracks a custom event when a user "considers" the burrito:
window.posthog?.capture("burrito_considered", {
total_considerations: newCount,
username: currentUser,
});This shows how to attach useful properties to events (e.g. counts, usernames).
Error tracking (src/pages/profile.astro)
The profile page includes a button to trigger a test error:
try {
throw new Error("Test error for PostHog error tracking");
} catch (err) {
window.posthog?.captureException(err);
}Logout and session reset (src/components/Header.astro)
On logout, both the local auth state and PostHog state are cleared:
window.posthog?.capture("user_logged_out");
localStorage.removeItem("currentUser");
window.posthog?.reset();posthog.reset() clears the current distinct ID and session so the next login starts a fresh identity.
Scripts
# Run dev server
npm run dev
# Build for production
npm run build
# Preview production build
npm run previewLearn more
---
.env.example
PUBLIC_POSTHOG_PROJECT_TOKEN=your_posthog_project_token_here
PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
---
astro.config.mjs
import { defineConfig } from "astro/config";
export default defineConfig({});
---
src/components/Header.astro
---
// Header component with navigation and logout functionality
---
<header class="header">
<div class="header-container">
<nav>
<a href="/">Home</a>
<a href="/burrito" class="auth-link" style="display: none;">Burrito Consideration</a>
<a href="/profile" class="auth-link" style="display: none;">Profile</a>
</nav>
<div class="user-section">
<span class="welcome-text" style="display: none;">Welcome, <span class="username"></span>!</span>
<span class="not-logged-in">Not logged in</span>
<button class="btn-logout" style="display: none;">Logout</button>
</div>
</div>
</header>
<script is:inline>
function updateHeader() {
const currentUser = localStorage.getItem('currentUser');
const authLinks = document.querySelectorAll('.auth-link');
const welcomeText = document.querySelector('.welcome-text');
const notLoggedIn = document.querySelector('.not-logged-in');
const logoutBtn = document.querySelector('.btn-logout');
const usernameSpan = document.querySelector('.username');
if (currentUser) {
authLinks.forEach(link => link.style.display = 'inline');
welcomeText.style.display = 'inline';
notLoggedIn.style.display = 'none';
logoutBtn.style.display = 'inline';
usernameSpan.textContent = currentUser;
} else {
authLinks.forEach(link => link.style.display = 'none');
welcomeText.style.display = 'none';
notLoggedIn.style.display = 'inline';
logoutBtn.style.display = 'none';
}
}
function handleLogout() {
const currentUser = localStorage.getItem('currentUser');
if (currentUser) {
window.posthog?.capture('user_logged_out');
}
localStorage.removeItem('currentUser');
localStorage.removeItem('burritoConsiderations');
// IMPORTANT: Reset the PostHog instance to clear the user session
window.posthog?.reset();
window.location.href = '/';
}
document.addEventListener('DOMContentLoaded', () => {
updateHeader();
document.querySelector('.btn-logout')?.addEventListener('click', handleLogout);
});
// Listen for storage changes (login/logout in other tabs)
window.addEventListener('storage', updateHeader);
</script>
<style>
.header {
background-color: #333;
color: white;
padding: 1rem;
}
.header-container {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.header nav {
display: flex;
gap: 1rem;
}
.header a {
color: white;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 4px;
transition: background-color 0.2s;
}
.header a:hover {
background-color: #555;
text-decoration: none;
}
.user-section {
display: flex;
align-items: center;
gap: 1rem;
}
.btn-logout {
background-color: #dc3545;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.btn-logout:hover {
background-color: #c82333;
}
</style>
---
src/components/posthog.astro
---
// PostHog analytics snippet
// Uses is:inline to prevent Astro from processing the script
---
<script is:inline define:vars={{ apiKey: import.meta.env.PUBLIC_POSTHOG_PROJECT_TOKEN, apiHost: import.meta.env.PUBLIC_POSTHOG_HOST }}>
!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.crossOrigin="anonymous",p.async=!0,p.src=s.api_host+"/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="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getFeatureFlag getFeatureFlagPayload reloadFeatureFlags group updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures getActiveMatchingSurveys getSurveys getNextSurveyStep onSessionId".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(apiKey || '', {
api_host: apiHost || 'https://us.i.posthog.com',
defaults: '2026-01-30'
})
</script>
---
src/layouts/PostHogLayout.astro
---
import PostHog from '../components/posthog.astro';
import Header from '../components/Header.astro';
import '../styles/global.css';
interface Props {
title: string;
}
const { title } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Astro PostHog Integration Example" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title}</title>
<PostHog />
</head>
<body>
<Header />
<main>
<slot />
</main>
</body>
</html>
---
src/lib/auth.ts
// Client-side auth utilities for localStorage-based authentication
export interface User {
username: string;
burritoConsiderations: number;
}
export function getCurrentUser(): User | null {
if (typeof window === "undefined") return null;
const username = localStorage.getItem("currentUser");
if (!username) return null;
const considerations = parseInt(
localStorage.getItem("burritoConsiderations") || "0",
10,
);
return {
username,
burritoConsiderations: considerations,
};
}
export function login(username: string, password: string): boolean {
if (!username || !password) return false;
localStorage.setItem("currentUser", username);
// Initialize burrito considerations if not set
if (!localStorage.getItem("burritoConsiderations")) {
localStorage.setItem("burritoConsiderations", "0");
}
return true;
}
export function logout(): void {
localStorage.removeItem("currentUser");
localStorage.removeItem("burritoConsiderations");
}
export function incrementBurritoConsiderations(): number {
const current = parseInt(
localStorage.getItem("burritoConsiderations") || "0",
10,
);
const newCount = current + 1;
localStorage.setItem("burritoConsiderations", newCount.toString());
return newCount;
}
---
src/pages/burrito.astro
---
import PostHogLayout from '../layouts/PostHogLayout.astro';
---
<PostHogLayout title="Burrito Consideration - Astro PostHog Example">
<div class="container">
<h1>Burrito consideration zone</h1>
<p>Take a moment to truly consider the potential of burritos.</p>
<div style="text-align: center;">
<button id="consider-btn" class="btn-burrito">
I have considered the burrito potential
</button>
<p id="success-message" class="success" style="display: none;">
Thank you for your consideration! Count: <span id="consideration-count"></span>
</p>
</div>
<div class="stats">
<h3>Consideration stats</h3>
<p>Total considerations: <span id="total-considerations">0</span></p>
</div>
</div>
</PostHogLayout>
<script is:inline>
function checkAuth() {
const currentUser = localStorage.getItem('currentUser');
if (!currentUser) {
window.location.href = '/';
return false;
}
return true;
}
function updateStats() {
const count = localStorage.getItem('burritoConsiderations') || '0';
document.getElementById('total-considerations').textContent = count;
}
function handleConsideration() {
const currentUser = localStorage.getItem('currentUser');
if (!currentUser) return;
// Increment the count
const currentCount = parseInt(localStorage.getItem('burritoConsiderations') || '0', 10);
const newCount = currentCount + 1;
localStorage.setItem('burritoConsiderations', newCount.toString());
// Update the UI
updateStats();
const successMessage = document.getElementById('success-message');
const considerationCount = document.getElementById('consideration-count');
considerationCount.textContent = newCount;
successMessage.style.display = 'block';
// Hide success message after 2 seconds
setTimeout(() => {
successMessage.style.display = 'none';
}, 2000);
// Capture burrito consideration event in PostHog
window.posthog?.capture('burrito_considered', {
total_considerations: newCount,
username: currentUser
});
}
document.addEventListener('DOMContentLoaded', () => {
if (!checkAuth()) return;
updateStats();
document.getElementById('consider-btn')?.addEventListener('click', handleConsideration);
});
</script>
---
src/pages/index.astro
---
import PostHogLayout from '../layouts/PostHogLayout.astro';
---
<PostHogLayout title="Home - Astro PostHog Example">
<div class="container">
<div id="logged-in-view" style="display: none;">
<h1>Welcome back, <span id="welcome-username"></span>!</h1>
<p>You are now logged in. Feel free to explore:</p>
<ul>
<li>Consider the potential of burritos</li>
<li>View your profile and statistics</li>
</ul>
</div>
<div id="logged-out-view">
<h1>Welcome to Burrito Consideration App</h1>
<p>Please sign in to begin your burrito journey</p>
<form id="login-form" class="form">
<div class="form-group">
<label for="username">Username:</label>
<input
type="text"
id="username"
placeholder="Enter any username"
required
/>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input
type="password"
id="password"
placeholder="Enter any password"
required
/>
</div>
<p id="error-message" class="error" style="display: none;"></p>
<button type="submit" class="btn-primary">Sign In</button>
</form>
<p class="note">
Note: This is a demo app. Use any username and password to sign in.
</p>
</div>
</div>
</PostHogLayout>
<script is:inline>
function updateView() {
const currentUser = localStorage.getItem('currentUser');
const loggedInView = document.getElementById('logged-in-view');
const loggedOutView = document.getElementById('logged-out-view');
const welcomeUsername = document.getElementById('welcome-username');
if (currentUser) {
loggedInView.style.display = 'block';
loggedOutView.style.display = 'none';
welcomeUsername.textContent = currentUser;
} else {
loggedInView.style.display = 'none';
loggedOutView.style.display = 'block';
}
}
function handleLogin(event) {
event.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const errorMessage = document.getElementById('error-message');
if (!username || !password) {
errorMessage.textContent = 'Please provide both username and password';
errorMessage.style.display = 'block';
return;
}
// Client-side only fake auth - store in localStorage
localStorage.setItem('currentUser', username);
if (!localStorage.getItem('burritoConsiderations')) {
localStorage.setItem('burritoConsiderations', '0');
}
// Identify the user in PostHog (once on login is enough)
window.posthog?.identify(username);
window.posthog?.capture('user_logged_in');
// Clear form
document.getElementById('username').value = '';
document.getElementById('password').value = '';
errorMessage.style.display = 'none';
// Update view
updateView();
// Trigger header update
window.dispatchEvent(new Event('storage'));
}
document.addEventListener('DOMContentLoaded', () => {
updateView();
document.getElementById('login-form')?.addEventListener('submit', handleLogin);
});
// Listen for storage changes
window.addEventListener('storage', updateView);
</script>
---
src/pages/profile.astro
---
import PostHogLayout from '../layouts/PostHogLayout.astro';
---
<PostHogLayout title="Profile - Astro PostHog Example">
<div class="container">
<h1>User Profile</h1>
<div class="stats">
<h2>Your Information</h2>
<p><strong>Username:</strong> <span id="profile-username"></span></p>
<p><strong>Burrito Considerations:</strong> <span id="profile-considerations">0</span></p>
</div>
<div style="margin-top: 2rem;">
<h3>Your Burrito Journey</h3>
<p id="journey-message"></p>
</div>
<div style="margin-top: 2rem;">
<h3>Error Tracking Demo</h3>
<p>Click the button below to trigger a test error and send it to PostHog:</p>
<button id="error-btn" class="btn-error">
Trigger Test Error
</button>
<p id="error-feedback" class="success" style="display: none;">
Error captured and sent to PostHog!
</p>
</div>
</div>
</PostHogLayout>
<script is:inline>
function checkAuth() {
const currentUser = localStorage.getItem('currentUser');
if (!currentUser) {
window.location.href = '/';
return false;
}
return true;
}
function updateProfile() {
const username = localStorage.getItem('currentUser') || '';
const considerations = parseInt(localStorage.getItem('burritoConsiderations') || '0', 10);
document.getElementById('profile-username').textContent = username;
document.getElementById('profile-considerations').textContent = considerations;
// Update journey message based on consideration count
const journeyMessage = document.getElementById('journey-message');
if (considerations === 0) {
journeyMessage.textContent = "You haven't considered any burritos yet. Visit the Burrito Consideration page to start!";
} else if (considerations === 1) {
journeyMessage.textContent = "You've considered the burrito potential once. Keep going!";
} else if (considerations < 5) {
journeyMessage.textContent = "You're getting the hang of burrito consideration!";
} else if (considerations < 10) {
journeyMessage.textContent = "You're becoming a burrito consideration expert!";
} else {
journeyMessage.textContent = "You are a true burrito consideration master!";
}
}
function triggerTestError() {
try {
throw new Error('Test error for PostHog error tracking');
} catch (err) {
// Capture the error in PostHog
window.posthog?.captureException(err);
console.error('Captured error:', err);
// Show feedback to user
const feedback = document.getElementById('error-feedback');
feedback.style.display = 'block';
setTimeout(() => {
feedback.style.display = 'none';
}, 3000);
}
}
document.addEventListener('DOMContentLoaded', () => {
if (!checkAuth()) return;
updateProfile();
document.getElementById('error-btn')?.addEventListener('click', triggerTestError);
});
</script>
---
Related skills
FAQ
What does instrument-product-analytics do?
instrument-product-analytics: A skill for development.
When should I use instrument-product-analytics?
When you need to use instrument-product-analytics for development tasks, or when instrument-product-analytics: a skill for development.
What are the main capabilities?
instrument-product-analytics.