Packages
posthog
2.5.0
2.16.2
2.16.1
2.16.0
2.15.1
2.15.0
2.14.2
2.14.1
2.14.0
2.13.0
2.12.1
2.12.0
2.11.1
2.11.0
2.10.3
2.10.2
2.10.1
2.10.0
2.9.2
2.9.1
2.9.0
2.8.4
2.8.3
2.8.2
2.8.1
2.8.0
2.7.1
2.7.0
2.6.1
2.6.0
2.5.0
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
Official PostHog Elixir SDK
Current section
Files
Jump to
Current section
Files
README.md
# PostHog Elixir SDK
[](https://hex.pm/packages/posthog)
[](https://hexdocs.pm/posthog)
A powerful Elixir SDK for [PostHog](https://posthog.com)
## Features
- Analytics and Feature Flags support
- Error tracking support
- Powerful process-based context propagation
- Asynchronous event sending with built-in batching
- Overridable HTTP client
- Support for multiple PostHog projects
## Getting Started
Add `PostHog` to your dependencies:
```elixir
def deps do
[
{:posthog, "~> 2.0"}
]
end
```
Configure the `PostHog` application environment:
```elixir
config :posthog,
enable: true,
enable_error_tracking: true,
api_host: "https://us.i.posthog.com", # Or `https://eu.i.posthog.com` or your self-hosted PostHog instance URL
api_key: "phc_my_api_key",
in_app_otp_apps: [:my_app]
```
For test environment, you want to enable test_mode:
```elixir
config :posthog,
test_mode: true
```
Optionally, enable [Plug integration](lib/posthog/integrations/plug.ex).
You're all set! 🎉 For more information on configuration, check the `PostHog.Config` module
documentation and the [advanced configuration guide](guides/advanced-configuration.md).
## Capturing Events
To capture an event, use `PostHog.capture/2`:
```elixir
iex> PostHog.capture("user_signed_up", %{distinct_id: "distinct_id_of_the_user"})
```
You can pass additional properties in the last argument:
```elixir
iex> PostHog.capture("user_signed_up", %{
distinct_id: "distinct_id_of_the_user",
login_type: "email",
is_free_trial: true
})
```
## Special Events
`PostHog.capture/2` is very powerful and allows you to send events that have
special meaning. For example:
### Create Alias
```elixir
iex> PostHog.capture("$create_alias", %{distinct_id: "frontend_id", alias: "backend_id"})
```
### Group Analytics
```elixir
iex> PostHog.capture("$groupidentify", %{
distinct_id: "static_string_used_for_all_group_events",
"$group_type": "company",
"$group_key": "company_id_in_your_db"
})
```
## 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 will automatically
attach these properties to any events you capture with `PostHog.capture/3`, as long as they
happen in the same process.
```elixir
iex> PostHog.set_context(%{distinct_id: "distinct_id_of_the_user"})
iex> PostHog.capture("page_opened")
```
You can scope context by event name. In this case, it will only be attached to a specific event:
```elixir
iex> PostHog.set_event_context("sensitive_event", %{"$process_person_profile": false})
```
You can always inspect the context:
```elixir
iex> PostHog.get_context()
%{distinct_id: "distinct_id_of_the_user"}
iex> PostHog.get_event_context("sensitive_event")
%{distinct_id: "distinct_id_of_the_user", "$process_person_profile": true}
```
## Feature Flags
`PostHog.FeatureFlags.check/2` is the main function for checking a feature flag.
```elixir
# Simple boolean feature flag
iex> PostHog.FeatureFlags.check("example-feature-flag-1", "user123")
{:ok, true}
# Note how it automatically sets `$feature/example-feature-flag-1` property in the context
iex> PostHog.get_context()
%{"$feature/example-feature-flag-1" => true}
# It will attempt to take distinct_id from the context if it's not provided
iex> PostHog.set_context(%{distinct_id: "user123"})
:ok
iex> PostHog.FeatureFlags.check("example-feature-flag-1")
{:ok, true}
# You can also pass a map with body parameters that will be sent to the /flags API as-is
iex> PostHog.FeatureFlags.check("example-feature-flag-1", %{distinct_id: "user123", groups: %{group_type: "group_id"}})
{:ok, true}
# It returns variant if it's set
iex> PostHog.FeatureFlags.check("example-feature-flag-2", "user123")
{:ok, "variant2"}
# Returns error if feature flag doesn't exist
iex> PostHog.FeatureFlags.check("example-feature-flag-3", "user123")
{:error, %PostHog.UnexpectedResponseError{message: "Feature flag example-feature-flag-3 was not found in the response", response: ...}}
```
If you're feeling adventurous and/or simply writing a script, you can use the `PostHog.FeatureFlags.check!/2` helper instead and it will return a boolean or raise an error.
```elixir
# Simple boolean feature flag
iex> PostHog.FeatureFlags.check!("example-feature-flag-1", "user123")
true
# Works for variants too
iex> PostHog.FeatureFlags.check!("example-feature-flag-2", "user123")
"variant2"
# Raises error if feature flag doesn't exist
iex> PostHog.FeatureFlags.check!("example-feature-flag-3", "user123")
** (PostHog.UnexpectedResponseError) Feature flag example-feature-flag-3 was not found in the response
```
### Getting the Full Flag Result
If you need more than just the value -- for example, the payload configured for a
flag or variant -- use `PostHog.FeatureFlags.get_feature_flag_result/2`:
```elixir
iex> PostHog.FeatureFlags.get_feature_flag_result("my-flag", "user123")
{:ok, %PostHog.FeatureFlags.Result{key: "my-flag", enabled: true, variant: nil, payload: nil}}
# Multivariant flag with a JSON payload
iex> PostHog.FeatureFlags.get_feature_flag_result("my-experiment", "user123")
{:ok, %PostHog.FeatureFlags.Result{key: "my-experiment", enabled: true, variant: "control", payload: %{"button_color" => "blue"}}}
# Flag not found
iex> PostHog.FeatureFlags.get_feature_flag_result("non-existent-flag", "user123")
{:ok, nil}
```
By default this sends a `$feature_flag_called` event, which PostHog uses to
track feature flag usage in your analytics, and to measure experiment exposure
when the flag is linked to an A/B test. You can opt out with `send_event: false`:
```elixir
iex> PostHog.FeatureFlags.get_feature_flag_result("my-flag", "user123", send_event: false)
{:ok, %PostHog.FeatureFlags.Result{key: "my-flag", enabled: true, variant: nil, payload: nil}}
```
A bang variant is also available:
```elixir
iex> PostHog.FeatureFlags.get_feature_flag_result!("my-flag", "user123")
%PostHog.FeatureFlags.Result{key: "my-flag", enabled: true, variant: nil, payload: nil}
```
## Error Tracking
Error Tracking is enabled by default.

You can always disable it by setting `enable_error_tracking` to false:
```elixir
config :posthog, enable_error_tracking: false
```
## Custom HTTP Client
The SDK uses [Req](https://hexdocs.pm/req) under the hood with gzip compression and
transient retry enabled by default. You can swap in your own HTTP client module to
change any of this behaviour — disable compression, add custom headers, attach
telemetry, or use a completely different HTTP library.
Set the `api_client_module` config option to a module that implements the
`PostHog.API.Client` behaviour:
```elixir
config :posthog, api_client_module: MyApp.PostHogClient
```
The simplest approach is to wrap the default client and override only what you need:
```elixir
defmodule MyApp.PostHogClient do
@behaviour PostHog.API.Client
@impl true
def client(api_key, api_host) do
default = PostHog.API.Client.client(api_key, api_host)
# Disable gzip compression
custom = Req.merge(default.client, compress_body: false)
%{default | client: custom}
end
@impl true
defdelegate request(client, method, url, opts), to: PostHog.API.Client
end
```
See `PostHog.API.Client` docs for more examples, including adding custom headers
and using a different HTTP library entirely.
## Multiple PostHog Projects
If your app works with multiple PostHog projects, PostHog can accommodate you. For
setup instructions, consult the [advanced configuration guide](guides/advanced-configuration.md).
## Developing locally
You should be able to fetch dependencies and run tests right away:
```
mix deps.get
mix test
```
To run integration test suite that sends real events to the API:
1. Create a test PostHog project and obtain an API key.
2. Create `config/integration.exs` config that will be used for integration tests:
```
cp config/integration.example.exs config/integration.exs
```
3. Put API key into `config/integration.exs`
4. Run integration tests
```
mix test --only integration
```
If you want to play with PostHog events in IEx, just create
`config/dev.override.exs` and tweak it to point to the instance of your liking.
This config will be gitignored. Here's a minimal example:
```elixir
# config/dev.override.exs
import Config
config :posthog,
enable: true
api_host: "https://us.i.posthog.com",
api_key: "phc_XXXX"
```
## Contributing
We welcome contributions! Here's how to get started:
1. Fork the repository and create your feature branch
2. Make your changes and ensure tests pass with `mix test`
3. Run `mix format` and `mix credo --strict` to ensure code quality
4. Open a Pull Request
### Creating Changesets
When making changes that should be included in the changelog, create a changeset:
```bash
# Install sampo CLI (requires Rust toolchain)
cargo install sampo
# Create a changeset describing your change
sampo add
```
Follow the prompts to specify:
- The type of change (`patch`, `minor`, or `major`)
- A description of the change for the changelog
Changesets are stored in `.sampo/changesets/` and will be consumed during the release process.
## Releasing
Releases are semi-automated using [Sampo](https://github.com/PostHog/sampo) and follow
the [PostHog SDK releases process](https://posthog.com/handbook/engineering/sdks/releases).
### How to trigger a release
1. **Add a changeset** to your PR describing the changes (see above)
2. **Add the `release` label** to the PR when it's ready for release
3. **Merge the PR** into `master`
Once merged, the release workflow will automatically:
- Consume all pending changesets
- Update the version in `mix.exs`
- Update `CHANGELOG.md` with the new entries
- Create a Git tag (e.g., `v2.2.0`)
- Create a GitHub Release with generated notes
- Publish the package to [Hex.pm](https://hex.pm/packages/posthog)
### Release approval
All releases require approval from the Client Libraries team via the `Release`
GitHub environment. Release requests are posted to `#approvals-client-libraries` on Slack.