Files
posthog.com/contents/docs/integrate/feature-flags-code/_snippets/feature-flags-code-api.mdx

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 />