or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

ai-integrations.mdclient-management.mdcontext-management.mdevent-tracking.mdfeature-flags.mdindex.mduser-group-management.md

feature-flags.mddocs/

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

```