mirror of
https://github.com/BillyOutlast/posthog.com.git
synced 2026-02-09 13:51:20 +01:00
515 lines
15 KiB
Plaintext
515 lines
15 KiB
Plaintext
There are 3 steps to implement feature flags using the PostHog API:
|
|
|
|
### Step 1: Evaluate the feature flag value using `flags`
|
|
|
|
`flags` is the endpoint used to determine if a given flag is enabled for a certain user or not.
|
|
|
|
#### Request
|
|
|
|
<MultiLanguage>
|
|
|
|
```shell
|
|
# Basic request (flags only)
|
|
curl -v -L --header "Content-Type: application/json" -d ' {
|
|
"api_key": "<ph_project_api_key>",
|
|
"distinct_id": "distinct_id_of_your_user",
|
|
"groups" : {
|
|
"group_type": "group_id"
|
|
}
|
|
}' "<ph_client_api_host>/flags?v=2"
|
|
|
|
# With configuration (flags + PostHog config)
|
|
curl -v -L --header "Content-Type: application/json" -d ' {
|
|
"api_key": "<ph_project_api_key>",
|
|
"distinct_id": "distinct_id_of_your_user",
|
|
"groups" : {
|
|
"group_type": "group_id"
|
|
}
|
|
}' "<ph_client_api_host>/flags?v=2&config=true"
|
|
```
|
|
|
|
```python
|
|
import requests
|
|
import json
|
|
|
|
# Basic request (flags only)
|
|
url = "<ph_client_api_host>/flags?v=2"
|
|
headers = {
|
|
"Content-Type": "application/json"
|
|
}
|
|
payload = {
|
|
"api_key": "<ph_project_api_key>",
|
|
"distinct_id": "user distinct id",
|
|
"groups": {
|
|
"group_type": "group_id"
|
|
}
|
|
}
|
|
response = requests.post(url, headers=headers, data=json.dumps(payload))
|
|
print(response.json())
|
|
|
|
# With configuration (flags + PostHog config)
|
|
url_with_config = "<ph_client_api_host>/flags?v=2&config=true"
|
|
response_with_config = requests.post(url_with_config, headers=headers, data=json.dumps(payload))
|
|
print(response_with_config.json())
|
|
```
|
|
|
|
```node
|
|
import fetch from "node-fetch";
|
|
|
|
async function sendFlagsRequest() {
|
|
const headers = {
|
|
"Content-Type": "application/json",
|
|
};
|
|
const payload = {
|
|
api_key: "<ph_project_api_key>",
|
|
distinct_id: "user distinct id",
|
|
groups: {
|
|
group_type: "group_id",
|
|
},
|
|
};
|
|
|
|
// Basic request (flags only)
|
|
const url = "<ph_client_api_host>/flags?v=2";
|
|
const response = await fetch(url, {
|
|
method: "POST",
|
|
headers: headers,
|
|
body: JSON.stringify(payload),
|
|
});
|
|
const data = await response.json();
|
|
console.log(data);
|
|
|
|
// With configuration (flags + PostHog config)
|
|
const urlWithConfig = "<ph_client_api_host>/flags?v=2&config=true";
|
|
const responseWithConfig = await fetch(urlWithConfig, {
|
|
method: "POST",
|
|
headers: headers,
|
|
body: JSON.stringify(payload),
|
|
});
|
|
const dataWithConfig = await responseWithConfig.json();
|
|
console.log(dataWithConfig);
|
|
}
|
|
|
|
sendFlagsRequest();
|
|
```
|
|
|
|
</MultiLanguage>
|
|
|
|
> **Note:** The `groups` key is only required for group-based feature flags. If you use it, replace `group_type` and `group_id` with the values for your group such as `company: "Twitter"`.
|
|
|
|
#### Using evaluation environment tags and runtime filtering without SDKs
|
|
|
|
When making direct API calls to the `/flags` endpoint, you can control which flags are evaluated using evaluation environment tags and runtime filtering.
|
|
|
|
##### Evaluation environments
|
|
|
|
To filter flags by evaluation environment, include the `evaluation_environments` field in your request body:
|
|
|
|
<MultiLanguage>
|
|
|
|
```shell
|
|
curl -v -L --header "Content-Type: application/json" -d ' {
|
|
"api_key": "<ph_project_api_key>",
|
|
"distinct_id": "distinct_id_of_your_user",
|
|
"evaluation_environments": ["production", "web"]
|
|
}' "<ph_client_api_host>/flags?v=2"
|
|
```
|
|
|
|
```python
|
|
import requests
|
|
import json
|
|
|
|
url = "<ph_client_api_host>/flags?v=2"
|
|
headers = {
|
|
"Content-Type": "application/json"
|
|
}
|
|
payload = {
|
|
"api_key": "<ph_project_api_key>",
|
|
"distinct_id": "user distinct id",
|
|
"evaluation_environments": ["production", "web"]
|
|
}
|
|
response = requests.post(url, headers=headers, data=json.dumps(payload))
|
|
print(response.json())
|
|
```
|
|
|
|
```javascript
|
|
const response = await fetch("<ph_client_api_host>/flags?v=2", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
api_key: "<ph_project_api_key>",
|
|
distinct_id: "user-distinct-id",
|
|
evaluation_environments: ["production", "web"]
|
|
}),
|
|
});
|
|
const data = await response.json();
|
|
```
|
|
|
|
</MultiLanguage>
|
|
|
|
Only flags where at least one evaluation tag matches (or flags with no tags at all) will be returned. For example:
|
|
- Flag with evaluation environment tags `["production", "api", "backend"]` + request with `["production", "web"]` = ✅ Flag evaluates ("production" matches)
|
|
- Flag with evaluation environment tags `["staging", "api"]` + request with `["production", "web"]` = ❌ Flag doesn't evaluate (no tags match)
|
|
- Flag with evaluation environment tags `["web", "mobile"]` + request with `["production", "web"]` = ✅ Flag evaluates ("web" matches)
|
|
- Flag with no evaluation environment tags = ✅ Always evaluates (backward compatibility)
|
|
|
|
##### Runtime detection
|
|
|
|
Evaluation runtime (server vs. client) is automatically detected based on your request headers and user-agent. This determines which flags are available based on their runtime setting (server-only, client-only, or all).
|
|
|
|
**How runtime is detected:**
|
|
|
|
1. **User-Agent patterns** - The system analyzes the User-Agent header:
|
|
- **Client-side patterns**: `Mozilla/`, `Chrome/`, `Safari/`, `Firefox/`, `Edge/` (browsers), or mobile SDKs like `posthog-android/`, `posthog-ios/`, `posthog-react-native/`, `posthog-flutter/`
|
|
- **Server-side patterns**: `posthog-python/`, `posthog-ruby/`, `posthog-php/`, `posthog-java/`, `posthog-go/`, `posthog-node/`, `posthog-dotnet/`, `posthog-elixir/`, `python-requests/`, `curl/`
|
|
|
|
2. **Browser-specific headers** - Presence of these headers indicates client-side:
|
|
- `Origin` header
|
|
- `Referer` header
|
|
- `Sec-Fetch-Mode` header
|
|
- `Sec-Fetch-Site` header
|
|
|
|
3. **Default behavior** - If runtime can't be determined, the system includes flags with no runtime requirement and those set to "all"
|
|
|
|
**Examples of runtime detection:**
|
|
|
|
```javascript
|
|
// Browser fetch - Detected as CLIENT runtime
|
|
// Will receive: client-only flags + "all" flags
|
|
// Won't receive: server-only flags
|
|
const response = await fetch("<ph_client_api_host>/flags?v=2", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
// Browser automatically adds Origin, Referer, Sec-Fetch-* headers
|
|
},
|
|
body: JSON.stringify({
|
|
api_key: "<ph_project_api_key>",
|
|
distinct_id: "user-id"
|
|
})
|
|
});
|
|
```
|
|
|
|
```python
|
|
# Python requests - Detected as SERVER runtime
|
|
# Will receive: server-only flags + "all" flags
|
|
# Won't receive: client-only flags
|
|
import requests
|
|
|
|
response = requests.post(
|
|
"<ph_client_api_host>/flags?v=2",
|
|
json={
|
|
"api_key": "<ph_project_api_key>",
|
|
"distinct_id": "user-id"
|
|
}
|
|
# python-requests/ in User-Agent indicates server-side
|
|
)
|
|
```
|
|
|
|
```shell
|
|
# curl - Detected as SERVER runtime
|
|
# Will receive: server-only flags + "all" flags
|
|
# Won't receive: client-only flags
|
|
curl -v -L --header "Content-Type: application/json" -d '{
|
|
"api_key": "<ph_project_api_key>",
|
|
"distinct_id": "user-id"
|
|
}' "<ph_client_api_host>/flags?v=2"
|
|
# curl/ in User-Agent indicates server-side
|
|
```
|
|
|
|
```javascript
|
|
// Node.js with custom User-Agent - Control runtime detection
|
|
const response = await fetch("<ph_client_api_host>/flags?v=2", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"User-Agent": "posthog-node/3.0.0" // Explicitly indicates server-side
|
|
},
|
|
body: JSON.stringify({
|
|
api_key: "<ph_project_api_key>",
|
|
distinct_id: "user-id"
|
|
})
|
|
});
|
|
```
|
|
|
|
##### Combining evaluation environment tags and runtime filtering
|
|
|
|
Both features work together as sequential filters:
|
|
|
|
```javascript
|
|
// Example: Production web client
|
|
const response = await fetch("<ph_client_api_host>/flags?v=2", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
// Browser headers will trigger client runtime detection
|
|
},
|
|
body: JSON.stringify({
|
|
api_key: "<ph_project_api_key>",
|
|
distinct_id: "user-id",
|
|
evaluation_environments: ["production", "web"]
|
|
})
|
|
});
|
|
|
|
// This request will only receive flags that:
|
|
// 1. Have runtime set to "client" OR "all" (due to browser headers)
|
|
// AND
|
|
// 2. Have evaluation environment tags matching "production" OR "web" (or no tags)
|
|
```
|
|
|
|
This allows precise control over which flags are evaluated in different contexts, helping optimize costs and improve security by ensuring flags only evaluate where intended.
|
|
|
|
#### Response
|
|
|
|
The response varies depending on whether you include the `config=true` query parameter:
|
|
|
|
##### Basic response (`/flags?v=2`)
|
|
|
|
Use this endpoint when you only need to evaluate feature flags. It returns a response with just the flag evaluation results:
|
|
|
|
```json
|
|
{
|
|
"flags": {
|
|
"my-awesome-flag": {
|
|
"key": "my-awesome-flag",
|
|
"enabled": true,
|
|
"reason": {
|
|
"code": "condition_match",
|
|
"condition_index": 0,
|
|
"description": "Condition set 1 matched"
|
|
},
|
|
"metadata": {
|
|
"id": 1,
|
|
"version": 1,
|
|
"payload": "{\"example\": \"json\", \"payload\": \"value\"}"
|
|
}
|
|
},
|
|
"my-multivariate-flag" :{
|
|
"key":"my-multivariate-flag",
|
|
"enabled": true,
|
|
"variant": "some-string-value",
|
|
"reason": {
|
|
"code": "condition_match",
|
|
"condition_index": 1,
|
|
"description": "Condition set 2 matched"
|
|
},
|
|
"metadata": {
|
|
"id": 2,
|
|
"version": 42,
|
|
}
|
|
},
|
|
"flag-thats-not-on": {
|
|
"key": "flag-thats-not-on",
|
|
"enabled": false,
|
|
"reason": {
|
|
"code": "no_condition_match",
|
|
"condition_index": 0,
|
|
"description": "No condition sets matched"
|
|
},
|
|
"metadata": {
|
|
"id": 3,
|
|
"version": 1
|
|
}
|
|
}
|
|
},
|
|
"errorsWhileComputingFlags": false,
|
|
"requestId": "550e8400-e29b-41d4-a716-446655440000"
|
|
}
|
|
```
|
|
|
|
##### Full response with configuration (`/flags?v=2&config=true`)
|
|
|
|
Use this endpoint when you need both feature flag evaluation and PostHog configuration information (useful for client-side SDKs that need to initialize PostHog):
|
|
|
|
```json
|
|
{
|
|
"config": {
|
|
"enable_collect_everything": true
|
|
},
|
|
"toolbarParams": {},
|
|
"errorsWhileComputingFlags": false,
|
|
"isAuthenticated": false,
|
|
"requestId": "550e8400-e29b-41d4-a716-446655440000",
|
|
"supportedCompression": [
|
|
"gzip",
|
|
"lz64"
|
|
],
|
|
"flags": {
|
|
"my-awesome-flag": {
|
|
"key": "my-awesome-flag",
|
|
"enabled": true,
|
|
"reason": {
|
|
"code": "condition_match",
|
|
"condition_index": 0,
|
|
"description": "Condition set 1 matched"
|
|
},
|
|
"metadata": {
|
|
"id": 1,
|
|
"version": 1,
|
|
"payload": "{\"example\": \"json\", \"payload\": \"value\"}"
|
|
}
|
|
},
|
|
"my-multivariate-flag" :{
|
|
"key":"my-multivariate-flag",
|
|
"enabled": true,
|
|
"variant": "some-string-value",
|
|
"reason": {
|
|
"code": "condition_match",
|
|
"condition_index": 1,
|
|
"description": "Condition set 2 matched"
|
|
},
|
|
"metadata": {
|
|
"id": 2,
|
|
"version": 42,
|
|
}
|
|
},
|
|
"flag-thats-not-on": {
|
|
"key": "flag-thats-not-on",
|
|
"enabled": false,
|
|
"reason": {
|
|
"code": "no_condition_match",
|
|
"condition_index": 0,
|
|
"description": "No condition sets matched"
|
|
},
|
|
"metadata": {
|
|
"id": 3,
|
|
"version": 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
> **Note:** `errorsWhileComputingFlags` will return `true` if we didn't manage to compute some flags (for example, if there's an [ongoing incident involving flag evaluation](https://status.posthog.com/)).
|
|
>
|
|
> This enables partial updates to currently active flags in your clients.
|
|
|
|
#### Quota limiting
|
|
|
|
If your organization exceeds its feature flag quota, the `/flags` endpoint will return a modified response with `quotaLimited`.
|
|
|
|
For basic response (`/flags?v=2`):
|
|
```json
|
|
{
|
|
"flags": {},
|
|
"errorsWhileComputingFlags": false,
|
|
"quotaLimited": ["feature_flags"],
|
|
"requestId": "d4d89b14-9619-4627-adf2-01b761691c2e"
|
|
}
|
|
```
|
|
|
|
For full response with configuration (`/flags?v=2&config=true`):
|
|
```json
|
|
{
|
|
"config": {
|
|
"enable_collect_everything": true
|
|
},
|
|
"toolbarParams": {},
|
|
"isAuthenticated": false,
|
|
"supportedCompression": [
|
|
"gzip",
|
|
"lz64"
|
|
],
|
|
"flags": {},
|
|
"errorsWhileComputingFlags": false,
|
|
"quotaLimited": ["feature_flags"],
|
|
"requestId": "d4d89b14-9619-4627-adf2-01b761691c2e"
|
|
// ... other fields, not relevant to feature flags
|
|
}
|
|
```
|
|
|
|
When you receive a response with `quotaLimited` containing `"feature_flags"`, it means:
|
|
1. Your feature flag evaluations have been temporarily paused because you've exceeded your feature flag quota
|
|
2. If you want to continue evaluating feature flags, you can increase your quota in [your billing settings](https://us.posthog.com/organization/billing) under **Feature flags & Experiments** or [contact support](https://us.posthog.com/#panel=support%3Asupport%3Abilling%3A%3Atrue)
|
|
|
|
import IncludePropertyInEvents from "./include-feature-flag-property-in-backend-events.mdx"
|
|
|
|
<IncludePropertyInEvents />
|
|
|
|
To do this, include the `$feature/feature_flag_name` property in your event:
|
|
|
|
<MultiLanguage>
|
|
|
|
```shell
|
|
curl -v -L --header "Content-Type: application/json" -d ' {
|
|
"api_key": "<ph_project_api_key>",
|
|
"event": "your_event_name",
|
|
"distinct_id": "distinct_id_of_your_user",
|
|
"properties": {
|
|
"$feature/feature-flag-key": "variant-key" # Replace feature-flag-key with your flag key. Replace 'variant-key' with the key of your variant
|
|
}
|
|
}' <ph_client_api_host>/i/v0/e/
|
|
```
|
|
|
|
```python
|
|
import requests
|
|
import json
|
|
|
|
url = "<ph_client_api_host>/i/v0/e/"
|
|
headers = {
|
|
"Content-Type": "application/json"
|
|
}
|
|
payload = {
|
|
"api_key": "<ph_project_api_key>",
|
|
"event": "your_event_name",
|
|
"distinct_id": "distinct_id_of_your_user,
|
|
"properties": {
|
|
"$feature/feature-flag-key": "variant-key" # Replace feature-flag-key with your flag key. Replace 'variant-key' with the key of your variant
|
|
}
|
|
}
|
|
response = requests.post(url, headers=headers, data=json.dumps(payload))
|
|
print(response)
|
|
```
|
|
|
|
</MultiLanguage>
|
|
|
|
### Step 3: Send a `$feature_flag_called` event
|
|
|
|
To track usage of your feature flag and view related analytics in PostHog, submit the `$feature_flag_called` event whenever you check a feature flag value in your code.
|
|
|
|
You need to include two properties with this event:
|
|
|
|
1. `$feature_flag_response`: This is the name of the variant the user has been assigned to e.g., "control" or "test"
|
|
2. `$feature_flag`: This is the key of the feature flag in your experiment.
|
|
|
|
<MultiLanguage>
|
|
|
|
```shell
|
|
curl -v -L --header "Content-Type: application/json" -d ' {
|
|
"api_key": "<ph_project_api_key>",
|
|
"event": "$feature_flag_called",
|
|
"distinct_id": "distinct_id_of_your_user",
|
|
"properties": {
|
|
"$feature_flag": "feature-flag-key",
|
|
"$feature_flag_response": "variant-name"
|
|
}
|
|
}' <ph_client_api_host>/i/v0/e/
|
|
```
|
|
|
|
```python
|
|
import requests
|
|
import json
|
|
|
|
url = "<ph_client_api_host>/i/v0/e/"
|
|
headers = {
|
|
"Content-Type": "application/json"
|
|
}
|
|
payload = {
|
|
"api_key": "<ph_project_api_key>",
|
|
"event": "feature_flag_called",
|
|
"distinct_id": "distinct_id_of_your_user,
|
|
"properties": {
|
|
"$feature_flag": "feature-flag-key",
|
|
"$feature_flag_response": "variant-name"
|
|
}
|
|
}
|
|
response = requests.post(url, headers=headers, data=json.dumps(payload))
|
|
print(response)
|
|
```
|
|
|
|
</MultiLanguage>
|
|
|
|
import APIOverrideServerProperties from './override-server-properties/api.mdx'
|
|
|
|
<APIOverrideServerProperties /> |