0
# Feature Flags
1
2
Comprehensive feature flag system supporting boolean flags, multivariate testing, remote configuration, and both local and remote evaluation. PostHog's feature flags enable controlled feature rollouts, A/B testing, and dynamic configuration management with built-in caching and fallback mechanisms.
3
4
## Capabilities
5
6
### Boolean Feature Flags
7
8
Check if a feature flag is enabled for a specific user with support for targeting and percentage rollouts.
9
10
```python { .api }
11
def feature_enabled(
12
key: str,
13
distinct_id: str,
14
groups: Optional[dict] = None,
15
person_properties: Optional[dict] = None,
16
group_properties: Optional[dict] = None,
17
only_evaluate_locally: bool = False,
18
send_feature_flag_events: bool = True,
19
disable_geoip: Optional[bool] = None
20
) -> bool:
21
"""
22
Use feature flags to enable or disable features for users.
23
24
Parameters:
25
- key: str - The feature flag key
26
- distinct_id: str - The user's distinct ID
27
- groups: Optional[dict] - Groups mapping from group type to group key
28
- person_properties: Optional[dict] - Person properties for evaluation
29
- group_properties: Optional[dict] - Group properties in format { group_type_name: { group_properties } }
30
- only_evaluate_locally: bool - Whether to evaluate only locally (default: False)
31
- send_feature_flag_events: bool - Whether to send feature flag events (default: True)
32
- disable_geoip: Optional[bool] - Whether to disable GeoIP lookup
33
34
Returns:
35
bool - True if the feature flag is enabled, False otherwise
36
37
Notes:
38
- Call load_feature_flags() before to avoid unexpected requests
39
- Automatically sends $feature_flag_called events unless disabled
40
"""
41
```
42
43
### Multivariate Feature Flags
44
45
Get feature flag variants for A/B testing and experiments with multiple treatment groups.
46
47
```python { .api }
48
def get_feature_flag(
49
key: str,
50
distinct_id: str,
51
groups: Optional[dict] = None,
52
person_properties: Optional[dict] = None,
53
group_properties: Optional[dict] = None,
54
only_evaluate_locally: bool = False,
55
send_feature_flag_events: bool = True,
56
disable_geoip: Optional[bool] = None
57
) -> Optional[FeatureFlag]:
58
"""
59
Get feature flag variant for users. Used with experiments.
60
61
Parameters:
62
- Same as feature_enabled()
63
64
Returns:
65
Optional[FeatureFlag] - FeatureFlag object with variant information, or None if not enabled
66
67
Notes:
68
- Returns variant string for multivariate flags
69
- Returns True for simple boolean flags
70
- Groups format: {"organization": "5"}
71
- Group properties format: {"organization": {"name": "PostHog", "employees": 11}}
72
"""
73
```
74
75
### Bulk Flag Operations
76
77
Retrieve all flags and their values for a user in a single operation for efficient bulk evaluation.
78
79
```python { .api }
80
def get_all_flags(
81
distinct_id: str,
82
groups: Optional[dict] = None,
83
person_properties: Optional[dict] = None,
84
group_properties: Optional[dict] = None,
85
only_evaluate_locally: bool = False,
86
disable_geoip: Optional[bool] = None
87
) -> Optional[dict[str, FeatureFlag]]:
88
"""
89
Get all flags for a given user.
90
91
Parameters:
92
- distinct_id: str - The user's distinct ID
93
- groups: Optional[dict] - Groups mapping
94
- person_properties: Optional[dict] - Person properties
95
- group_properties: Optional[dict] - Group properties
96
- only_evaluate_locally: bool - Whether to evaluate only locally
97
- disable_geoip: Optional[bool] - Whether to disable GeoIP lookup
98
99
Returns:
100
Optional[dict[str, FeatureFlag]] - Dictionary mapping flag keys to FeatureFlag objects
101
102
Notes:
103
- More efficient than individual flag calls
104
- Does not send feature flag events
105
- Flags are key-value pairs where value is variant, True, or False
106
"""
107
108
def get_all_flags_and_payloads(
109
distinct_id: str,
110
groups: Optional[dict] = None,
111
person_properties: Optional[dict] = None,
112
group_properties: Optional[dict] = None,
113
only_evaluate_locally: bool = False,
114
disable_geoip: Optional[bool] = None
115
) -> FlagsAndPayloads:
116
"""
117
Get all flags and their payloads for a user.
118
119
Parameters:
120
- Same as get_all_flags()
121
122
Returns:
123
FlagsAndPayloads - Object with featureFlags and featureFlagPayloads dictionaries
124
"""
125
```
126
127
### Feature Flag Results with Payloads
128
129
Get complete feature flag information including variants, payloads, and evaluation reasons.
130
131
```python { .api }
132
def get_feature_flag_result(
133
key: str,
134
distinct_id: str,
135
groups: Optional[dict] = None,
136
person_properties: Optional[dict] = None,
137
group_properties: Optional[dict] = None,
138
only_evaluate_locally: bool = False,
139
send_feature_flag_events: bool = True,
140
disable_geoip: Optional[bool] = None
141
) -> Optional[FeatureFlagResult]:
142
"""
143
Get a FeatureFlagResult object which contains the flag result and payload.
144
145
Parameters:
146
- Same as feature_enabled()
147
148
Returns:
149
Optional[FeatureFlagResult] - Complete flag result with enabled, variant, payload, key, and reason
150
151
Notes:
152
- Most comprehensive flag evaluation method
153
- Includes automatic JSON deserialization of payloads
154
- Provides evaluation reason for debugging
155
"""
156
157
def get_feature_flag_payload(
158
key: str,
159
distinct_id: str,
160
match_value: Optional[str] = None,
161
groups: Optional[dict] = None,
162
person_properties: Optional[dict] = None,
163
group_properties: Optional[dict] = None,
164
only_evaluate_locally: bool = False,
165
send_feature_flag_events: bool = True,
166
disable_geoip: Optional[bool] = None
167
) -> Optional[str]:
168
"""
169
Get the payload for a feature flag.
170
171
Parameters:
172
- key: str - The feature flag key
173
- distinct_id: str - The user's distinct ID
174
- match_value: Optional[str] - Expected flag value for payload retrieval
175
- Other parameters same as feature_enabled()
176
177
Returns:
178
Optional[str] - The payload string, or None if flag not enabled or no payload
179
"""
180
```
181
182
### Remote Configuration
183
184
Access remote configuration flags that don't require user evaluation, useful for application-wide settings.
185
186
```python { .api }
187
def get_remote_config_payload(key: str) -> Optional[str]:
188
"""
189
Get the payload for a remote config feature flag.
190
191
Parameters:
192
- key: str - The key of the feature flag
193
194
Returns:
195
Optional[str] - The payload associated with the feature flag, decrypted if encrypted
196
197
Notes:
198
- Requires personal_api_key to be set for authentication
199
- Used for application-wide configuration
200
- Does not require user evaluation
201
"""
202
```
203
204
### Flag Management
205
206
Load and inspect feature flag definitions for debugging and management.
207
208
```python { .api }
209
def load_feature_flags():
210
"""
211
Load feature flag definitions from PostHog.
212
213
Notes:
214
- Fetches latest flag definitions from server
215
- Updates local cache for faster evaluation
216
- Should be called on application startup
217
- Enables local evaluation for better performance
218
"""
219
220
def feature_flag_definitions():
221
"""
222
Returns loaded feature flags.
223
224
Returns:
225
dict - Currently loaded feature flag definitions
226
227
Notes:
228
- Helpful for debugging what flag information is loaded
229
- Shows flag keys, conditions, and rollout percentages
230
- Returns empty dict if no flags loaded
231
"""
232
```
233
234
## Usage Examples
235
236
### Basic Feature Flag Usage
237
238
```python
239
import posthog
240
241
# Configure PostHog
242
posthog.api_key = 'phc_your_project_api_key'
243
posthog.personal_api_key = 'phc_your_personal_api_key' # For remote config
244
245
# Load flags on startup
246
posthog.load_feature_flags()
247
248
# Simple boolean flag check
249
if posthog.feature_enabled('new-checkout', 'user123'):
250
# Show new checkout flow
251
render_new_checkout()
252
else:
253
# Show existing checkout
254
render_old_checkout()
255
256
# Check flag with user properties
257
enabled = posthog.feature_enabled(
258
'premium-features',
259
'user123',
260
person_properties={'plan': 'premium', 'region': 'us'}
261
)
262
263
if enabled:
264
show_premium_features()
265
```
266
267
### Multivariate Flag Testing
268
269
```python
270
import posthog
271
272
# Get flag variant for A/B testing
273
variant = posthog.get_feature_flag('checkout-design', 'user123')
274
275
if variant == 'variant-a':
276
render_checkout_design_a()
277
elif variant == 'variant-b':
278
render_checkout_design_b()
279
elif variant == 'control':
280
render_original_checkout()
281
else:
282
# Flag not enabled or no variant matched
283
render_original_checkout()
284
285
# Get variant with payload
286
result = posthog.get_feature_flag_result('pricing-test', 'user123')
287
288
if result and result.enabled:
289
variant = result.variant # 'control', 'test-a', 'test-b'
290
config = result.payload # {'discount': 10, 'button_color': 'red'}
291
292
render_pricing_page(variant, config)
293
```
294
295
### Group-Based Feature Flags
296
297
```python
298
import posthog
299
300
# Feature flag with company-level targeting
301
enabled = posthog.feature_enabled(
302
'enterprise-features',
303
'user123',
304
groups={'company': 'acme_corp'},
305
person_properties={'role': 'admin'},
306
group_properties={
307
'company': {
308
'plan': 'enterprise',
309
'employees': 500,
310
'industry': 'technology'
311
}
312
}
313
)
314
315
if enabled:
316
show_enterprise_dashboard()
317
318
# Multi-level group targeting
319
variant = posthog.get_feature_flag(
320
'ui-redesign',
321
'user123',
322
groups={
323
'company': 'acme_corp',
324
'team': 'engineering',
325
'region': 'us-west'
326
},
327
group_properties={
328
'company': {'size': 'large'},
329
'team': {'department': 'product'},
330
'region': {'timezone': 'PST'}
331
}
332
)
333
```
334
335
### Bulk Flag Evaluation
336
337
```python
338
import posthog
339
340
# Get all flags for efficient evaluation
341
all_flags = posthog.get_all_flags(
342
'user123',
343
groups={'company': 'acme_corp'},
344
person_properties={'plan': 'premium'}
345
)
346
347
if all_flags:
348
# Check multiple flags efficiently
349
features = {
350
'new_ui': all_flags.get('new-ui', False),
351
'beta_features': all_flags.get('beta-features', False),
352
'advanced_analytics': all_flags.get('advanced-analytics', False)
353
}
354
355
configure_user_interface(features)
356
357
# Get flags with payloads
358
flags_and_payloads = posthog.get_all_flags_and_payloads(
359
'user123',
360
person_properties={'segment': 'power_user'}
361
)
362
363
feature_flags = flags_and_payloads['featureFlags']
364
payloads = flags_and_payloads['featureFlagPayloads']
365
366
# Configure features with their payloads
367
for flag_key, enabled in feature_flags.items():
368
if enabled and flag_key in payloads:
369
configure_feature(flag_key, payloads[flag_key])
370
```
371
372
### Remote Configuration
373
374
```python
375
import posthog
376
377
# Application-wide configuration
378
api_rate_limit_config = posthog.get_remote_config_payload('api-rate-limits')
379
if api_rate_limit_config:
380
import json
381
config = json.loads(api_rate_limit_config)
382
set_rate_limits(config['requests_per_minute'])
383
384
# Feature configuration without user context
385
maintenance_mode = posthog.get_remote_config_payload('maintenance-mode')
386
if maintenance_mode == 'enabled':
387
show_maintenance_page()
388
```
389
390
### Local vs Remote Evaluation
391
392
```python
393
import posthog
394
395
# Force local evaluation only (faster, but may be stale)
396
local_result = posthog.feature_enabled(
397
'new-feature',
398
'user123',
399
only_evaluate_locally=True
400
)
401
402
# Allow remote evaluation (slower, but always current)
403
remote_result = posthog.feature_enabled(
404
'new-feature',
405
'user123',
406
only_evaluate_locally=False # Default behavior
407
)
408
409
# Disable automatic event tracking
410
silent_check = posthog.feature_enabled(
411
'internal-flag',
412
'user123',
413
send_feature_flag_events=False
414
)
415
```
416
417
### Advanced Configuration
418
419
```python
420
import posthog
421
422
# Configure flag polling
423
posthog.poll_interval = 60 # Check for flag updates every 60 seconds
424
posthog.enable_local_evaluation = True # Enable local flag evaluation
425
426
# Custom error handling
427
def flag_error_handler(error):
428
print(f"Feature flag error: {error}")
429
# Log to monitoring service
430
log_error("feature_flag_error", str(error))
431
432
posthog.on_error = flag_error_handler
433
434
# Load flags with error handling
435
try:
436
posthog.load_feature_flags()
437
except Exception as e:
438
print(f"Failed to load feature flags: {e}")
439
# Continue with default behavior
440
```
441
442
## Flag Types and Data Structures
443
444
### FeatureFlag Object
445
446
```python
447
from posthog.types import FeatureFlag, FlagReason, FlagMetadata
448
449
# FeatureFlag structure
450
flag = FeatureFlag(
451
key='test-flag',
452
enabled=True,
453
variant='test-variant',
454
reason=FlagReason(
455
code='CONDITION_MATCH',
456
condition_index=0,
457
description='User matched condition 1'
458
),
459
metadata=FlagMetadata(
460
id=123,
461
payload='{"config": "value"}',
462
version=1,
463
description='Test flag for A/B testing'
464
)
465
)
466
467
# Access flag properties
468
flag_value = flag.get_value() # Returns variant or enabled boolean
469
```
470
471
### FeatureFlagResult Object
472
473
```python
474
from posthog.types import FeatureFlagResult
475
476
# FeatureFlagResult structure (most comprehensive)
477
result = FeatureFlagResult(
478
key='pricing-test',
479
enabled=True,
480
variant='test-variant',
481
payload={'discount': 15, 'color': 'blue'},
482
reason='User in test group A'
483
)
484
485
# Access result properties
486
value = result.get_value() # 'test-variant'
487
config = result.payload # {'discount': 15, 'color': 'blue'}
488
```
489
490
## Best Practices
491
492
### Flag Naming and Organization
493
494
```python
495
# Good - descriptive, hierarchical naming
496
posthog.feature_enabled('checkout-redesign-v2', 'user123')
497
posthog.feature_enabled('billing-monthly-invoicing', 'user123')
498
posthog.feature_enabled('analytics-real-time-data', 'user123')
499
500
# Avoid - vague or inconsistent naming
501
posthog.feature_enabled('flag1', 'user123')
502
posthog.feature_enabled('NewFeature', 'user123') # Inconsistent case
503
```
504
505
### Performance Optimization
506
507
```python
508
# Load flags once on application startup
509
posthog.load_feature_flags()
510
511
# Use bulk evaluation for multiple flags
512
all_flags = posthog.get_all_flags('user123')
513
514
# Enable local evaluation for better performance
515
posthog.enable_local_evaluation = True
516
517
# Use appropriate evaluation mode
518
# Local: faster, may be slightly stale
519
local_check = posthog.feature_enabled('flag', 'user', only_evaluate_locally=True)
520
521
# Remote: slower, always current
522
remote_check = posthog.feature_enabled('flag', 'user', only_evaluate_locally=False)
523
```
524
525
### Error Handling and Fallbacks
526
527
```python
528
def check_feature_with_fallback(flag_key, user_id, default=False):
529
try:
530
return posthog.feature_enabled(flag_key, user_id)
531
except Exception as e:
532
print(f"Feature flag check failed: {e}")
533
return default
534
535
# Use safe defaults
536
new_ui_enabled = check_feature_with_fallback('new-ui', 'user123', default=False)
537
```
538
539
### Testing and Debugging
540
541
```python
542
# Check loaded flag definitions
543
definitions = posthog.feature_flag_definitions()
544
print(f"Loaded {len(definitions)} feature flags")
545
546
# Debug flag evaluation
547
result = posthog.get_feature_flag_result('debug-flag', 'user123')
548
if result:
549
print(f"Flag: {result.key}")
550
print(f"Enabled: {result.enabled}")
551
print(f"Variant: {result.variant}")
552
print(f"Reason: {result.reason}")
553
print(f"Payload: {result.payload}")
554
```