This repository contains a set of packages for interacting with the PostHog API in .NET applications. This README is for those who wish to contribute to these packages.
For documentation on the specific packages, see the README files in the respective package directories.
| Package | Version | Description |
|---|---|---|
| PostHog.AspNetCore | For use in ASP.NET Core projects. | |
| PostHog | The core library. Over time, this will support client environments such as Unit, Xamarin, etc. | |
| PostHog.AI | AI Observability for OpenAI and other LLM providers. |
Warning
These packages are currently in a pre-release stage. We're making them available publicly to solicit feedback. While we always strive to maintain a high level of quality, use these packages at your own risk. There will be many breaking changes until we reach a stable release.
The core PostHog package targets netstandard2.1 and net8.0 for broad compatibility. The PostHog.AspNetCore package targets net8.0. The PostHog.AI package targets netstandard2.1 and net8.0 for broad compatibility.
To build the solution, run the following commands in the root of the repository:
$ dotnet restore
$ dotnet buildMore detailed docs for using this library can be found at PostHog Docs for the .NET Client SDK.
Sample projects are located in the samples directory.
To run the samples, you'll need to set your PostHog project API key. From the repository root you can run:
bin/user-secrets set PostHog:ProjectApiKey YOUR_API_KEYThe main ASP.NET Core sample app can be run with the following command:
$ bin/startYou can also run it from your favorite IDE or editor.
To run the tests, run the following command in the root of the repository:
$ dotnet testThe test projects target both net8.0 and netcoreapp3.1. While .NET Core 3.1 reached end-of-life in December 2022, we continue to test against it because:
- It was the first runtime to fully support .NET Standard 2.1
- It serves as our minimum test baseline to ensure the
netstandard2.1library works correctly on older runtimes - It helps catch compatibility issues that might not surface on newer runtimes
This testing approach ensures broad compatibility without requiring users to install legacy runtimes in production.
Releases are driven by PR labels. When a PR with the right labels is merged to main, a GitHub Actions workflow handles version bumping, tagging, creating a GitHub Release (with auto-generated notes), and publishing to NuGet.
- Add the
releaselabel and exactly one ofbump-patch,bump-minor, orbump-majorto your PR - Merge the PR to
main - Approve the release in the GitHub Environment gate (the workflow pauses for maintainer approval)
- The workflow bumps the version in
Directory.Build.props, commits tomain, creates a git tag, and creates a GitHub Release - The GitHub Release triggers the
main.yamlworkflow, which builds and publishes the packages to NuGet
If you need to perform an emergency release bypassing the automated workflow, the bin/release-local script is available:
# For a patch release (1.0.6 -> 1.0.7)
./bin/release-local patch
# For a minor release (1.0.6 -> 1.1.0)
./bin/release-local minor
# For a major release (1.0.6 -> 2.0.0)
./bin/release-local majorFor ASP.NET Core projects, install the PostHog.AspNetCore package:
$ dotnet add package PostHog.AspNetCoreAnd register the PostHog services in Program.cs (or Startup.cs) file by calling the AddPostHog extension
method on IHostApplicationBuilder like so:
using PostHog;
var builder = WebApplication.CreateBuilder(args);
builder.AddPostHog();For other .NET projects, install the PostHog package:
$ dotnet add package PostHogAnd if your project supports dependency injection, register the PostHog services in Program.cs (or Startup.cs)
file by calling the AddPostHog extension method on IServiceCollection. Here's an example for a console app:
using PostHog;
var services = new ServiceCollection();
services.AddPostHog();
var serviceProvider = services.BuildServiceProvider();
var posthog = serviceProvider.GetRequiredService<IPostHogClient>();For a console app (or apps not using dependency injection), you can also use the PostHogClient directly, just make
sure it's a singleton:
using System;
using PostHog;
var posthog = new PostHogClient(Environment.GetEnvironmentVariable("PostHog__PersonalApiKey"));The AddPostHog methods accept an optional Action<PostHogOptions> parameter that you can use to configure the
client. For examples, check out the HogTied.Web sample project and the unit tests.
Inject the IPostHogClient interface into your controller or page:
posthog.Capture(userId, "user signed up", new() { ["plan"] = "pro" });client.CapturePageView(userId, Request.Path.Value ?? "Unknown");See the Identifying users for more information about identifying users.
Identifying a user typically happens on the front-end. For example, when an authenticated user logs in, you can call identify to associate the user with their previously anonymous actions.
When identify is called the first-time for a distinct id, PostHog will create a new user profile. If the user already exists, PostHog will update the user profile with the new data. So the typical usage of IdentifyAsync here will be to update the person properties that PostHog knows about your user.
await posthog.IdentifyAsync(
userId,
new()
{
["email"] = "haacked@posthog.com",
["name"] = "Phil Haack",
["plan"] = "pro"
});Use the Alias method to associate one identity with another. This is useful when a user logs in and you want to associate their anonymous actions with their authenticated actions.
await posthog.AliasAsync(sessionId, userId);Note that capturing events is designed to be fast and done in the background. You can configure how often batches are sent to the PostHog API using the FlushAt and FlushInterval settings.
posthog.Capture(userId, "user signed up", new() { ["plan"] = "pro" });posthog.CapturePageView(userId, Request.Path.Value ?? "Unknown");posthog.CaptureScreen(userId, "Main Screen");Check if the awesome-new-feature feature flag is enabled for the user with the id userId.
var enabled = await posthog.IsFeatureEnabledAsync(userId, "awesome-new-feature");You can override properties of the user stored on PostHog servers for the purposes of feature flag evaluation. For example, suppose you offer a temporary pro-plan for the duration of the user's session. You might do this:
if (await posthog.IsFeatureEnabledAsync(
"pro-feature",
"some-user-id",
personProperties: new() { ["plan"] = "pro" }))
{
// Access to pro feature
}If you have group analytics enabled, you can also override group properties.
if (await posthog.IsFeatureEnabledAsync(
"large-project-feature",
"some-user-id",
new FeatureFlagOptions
{
Groups = [new Group(groupType: "project", groupKey: "project-group-key") { ["size"] = "large" }]
}))
{
// Access large project feature
}Note
Specifying PersonProperties and GroupProperties is necessary when using local evaluation of feature flags.
Some feature flags may have associated payloads.
if (await posthog.GetFeatureFlagAsync("awesome-new-feature", "some-user-id") is { Payload: {} payload })
{
// Do something with the payload.
Console.WriteLine($"The payload is: {payload}");
}Using information on the PostHog server.
var flags = await posthog.GetAllFeatureFlagsAsync("some-user-id");Overriding the group properties for the current user.
var flags = await posthog.GetAllFeatureFlagsAsync(
"some-user-id",
options: new AllFeatureFlagsOptions
{
Groups =
[
new Group("project", "aaaa-bbbb-cccc")
{
["$group_key"] = "aaaa-bbbb-cccc",
["size"] = "large"
}
]
});