# Cutting feature flag costs - Docs

PostHog Feature Flags comes with a generous free tier and transparent, usage-based pricing. Our large free tier means more than 90% of companies *use PostHog for free*.

No credit card is required to get started. You can also set billing limits to avoid any surprise charges.

Feature Flags is billed by the number of API requests you make to evaluate flags. The price per request changes based on your usage. You can estimate your costs using our pricing calculator below or by visiting our [pricing page](/pricing.md) for a more detailed breakdown.

1,000,000requests/month

$0

1M10M100M1B

#### Allocation

#### Price

#### Your selection

#### Subtotal

##### First 1 million requests

##### **$0.000000**/request

1,000,000

$0

##### 1-2 million

##### **$0.000100**/request

0

$0

##### 2-10 million

##### **$0.000045**/request

0

$0

##### 10-50 million

##### **$0.000025**/request

0

$0

##### 50 million+

##### **$0.000010**/request

0

$0

We aim to be significantly cheaper than our competitors. Below are tips to reduce your feature flag costs.

## Understanding your bill

Feature flags are charged based on requests made to our `/flags` endpoint, which is used to evaluate feature flags for a given user. This endpoint is called in several scenarios:

-   When explicitly requesting feature flags via server-side SDK methods like `getFeatureFlag()`, `getAllFlags()`, or `isFeatureEnabled()`
-   When using client-side SDK methods like `onFeatureFlags()` or when flags are automatically fetched
-   Automatically on SDK initialization (unless disabled, see below)
-   When evaluating survey targeting, which checks **all active feature flags** in your project

**Client-side vs server-side billing**

For **client-side SDKs** (JavaScript, React Native), methods like `getFeatureFlag()` don't necessarily create billable events due to caching. Requests are made when the SDK initializes, when users are identified, or when `reloadFeatureFlags()` is called.

For **server-side SDKs** (Node, Python, PHP, etc.), each call to `getFeatureFlag()`, `getAllFlags()`, or `isFeatureEnabled()` makes a request to the `/flags` endpoint and incurs a billable event.

Feature flags are **not** billed based on `$feature_flag_called` events, which are optional events sent for metrics tracking, not for actual feature flag evaluation.

The number of `/flags` requests and the number of `$feature_flag_called` events are **not** directly related.

**Unused flags still incur charges**

Active feature flags you're not using in your code are still evaluated (and charged) every time the `/flags` endpoint is called. This includes when surveys are loaded, as survey targeting evaluates all active flags to determine eligibility.

To stop charges for unused flags, you must **disable, delete, or archive** them in the PostHog UI – simply removing them from your code is not enough.

### Creating a billable usage dashboard

Want to know exactly what's driving your bill? Create a dashboard with the [PostHog billable usage template](/templates/posthog-billable-usage.md) to break down and analyze your usage across different events, SDK libraries, and products.

![PostHog billable usage dashboard](https://res.cloudinary.com/dmukukwp6/image/upload/posthog_billable_usage_b2b494d4bb.png)![PostHog billable usage dashboard](https://res.cloudinary.com/dmukukwp6/image/upload/posthog_billable_usage_b2b494d4bb.png)

This dashboard turns your billing usage into a live, interactive report — so you can create insights, spot spikes, and optimize how you're spending.

Estimating your bill manually (with math!)

#### Frontend SDKs

We make a request to fetch feature flags (using the [`/flags` endpoint](/docs/api/flags.md)) when one of the below occurs:

-   The PostHog SDK is initialized
-   A user is [identified](/docs/data/identify.md)
-   A user's [properties](/docs/product-analytics/person-properties.md) are updated
-   You call `posthog.reloadFeatureFlags()` in your code
-   You create a [group](/docs/product-analytics/group-analytics.md)

For the [JavaScript web SDK](/docs/libraries/js.md), you can estimate how many feature flag requests you will make by doing the following:

1.  Check the networks tab in Chrome inspector tools for the number of `/flags` requests
2.  Find out your number of monthly page views
3.  Multiply your number of `/flags` requests by your monthly page views

For example, if on refresh, you see 2 `/flags` requests per pageview and you have ~150,000 pageviews, your monthly feature flag requests should be around ~300,000.

#### Backend SDKs

##### Without local evaluation

If you're not using [local evaluation](/docs/feature-flags/local-evaluation.md), a request to get feature flags is made every time you call `posthog.get_feature_flag()` on your backend. Your estimated usage will be however many times you are calling this code.

##### With local evaluation

If you're using [local evaluation](/docs/feature-flags/local-evaluation.md), each local evaluation request is equivalent to, and charged as, 10 feature flag requests. Local evaluation calls, by default, are made every 30 seconds. Assuming your server is constantly running and making 2 requests per minute, you will be charged `10 * 2 * 60 * 24 * 30 = 864,000 feature flag requests` each month.

> **Note:** This figure is for a single server and a single instance of PostHog. If you have multiple servers, PostHog instances, or a different poll duration, you need to multiply your estimates too.

## Reducing client-side feature flag requests

In our [JavaScript Web](/docs/libraries/js.md) and [React Native](/docs/libraries/react-native.md) SDKs, you can reduce the cost of feature flags by reducing the number of requests your client makes to fetch feature flag values for your users.

You can do the following using the [advanced configuration](/docs/libraries/js/config.md#advanced-configuration):

1.  Set `advanced_disable_feature_flags_on_first_load` to `true`. This prevents the SDK from automatically requesting feature flags during its initial initialization. Note that other SDK methods (like `posthog.identify()`) will still trigger flag requests when called. This is useful when you're immediately calling `identify()` anyway, since that requests flags on its own.

2.  Set `advanced_disable_feature_flags` to `true`. This stops PostHog automatically requesting feature flags. Instead, use [bootstrapping](/docs/feature-flags/bootstrapping.md) to load flags exactly once.

3.  Set `advanced_only_evaluate_survey_feature_flags` to `true`. When enabled, only survey-related feature flags are evaluated, which significantly reduces costs if you primarily use PostHog for surveys. This disables evaluation of regular feature flags while still allowing surveys to function properly.

> **Note:** When using `advanced_disable_feature_flags_on_first_load` or `advanced_only_evaluate_survey_feature_flags`, the SDK still makes a request to the `/flags` endpoint, but flags won't be evaluated and you won't be charged for that request.

**Surveys use feature flags**

Surveys rely on feature flags internally. Using `advanced_disable_feature_flags` will disable surveys. If you use surveys, set `advanced_only_evaluate_survey_feature_flags` instead to only evaluate survey-related flags while skipping others.

## Reducing local evaluation costs

If you're using [local evaluation](/docs/feature-flags/local-evaluation.md), your bill may be high because of too many requests to fetch feature flag definitions. By default, PostHog fetches these every 30 seconds.

Each request charges 10 credits, so assuming your server is constantly running and making 2 requests per minute (the default setting), this will charge `10 * 2 * 60 * 24 * 30 = 864,000 credits` each month.

You can reduce this by increasing the [feature flag polling interval](/docs/feature-flags/local-evaluation.md#step-2-initialize-posthog-with-your-personal-api-key) when initializing PostHog. For example, every 5 minutes instead of every 30 seconds.

The drawback of this approach is that whenever you make an update to a feature flag using the PostHog UI, it takes 5 minutes (instead of 30 seconds) for that change to roll to your server.

**Edge/Lambda environments**

Do not use local evaluation in an edge or Lambda environment, as this initializes a PostHog instance on every call, which can raise your bill drastically. It's best to use regular flag evaluation instead.

## Quota limiting

Like all PostHog products, you can set a [billing limit](https://us.posthog.com/organization/billing) for feature flags. This limit is set at the **organization level** and shared across all projects in your organization. When your organization exceeds this limit, the `/flags` endpoint will return a `quota_limited` response like this:

JSON

PostHog AI

```json
{
  "quota_limited": true,
  "featureFlags": {}
}
```

In this case, SDKs will:

1.  Return the default value for the feature flag, like `false` for `isFeatureEnabled()` and `null` for `getFeatureFlag()`
2.  Include a `quota_limited` property in the response
3.  Log a warning message if debug mode is enabled

This ensures that your application continues to function even when feature flag quotas are exceeded, falling back to default behavior rather than failing.

## Configuring test and CI environments

Test environments, staging servers, and CI pipelines can silently accumulate feature flag requests. Since these environments often don't need real-time flag evaluation, you can configure the SDK to minimize or eliminate `/flags` requests entirely.

The key settings are:

-   **JavaScript**: `advanced_disable_feature_flags: true` stops the SDK from hitting `/flags` on init
-   **Mobile SDKs**: `preloadFeatureFlags = false` prevents automatic flag fetching
-   **All SDKs**: Use flag overrides or bootstrapping to provide values locally without network requests

PostHog AI

### Web

```javascript
// Detect test/CI environment
const isTestEnv = process.env.NODE_ENV === 'test' || process.env.CI === 'true'
posthog.init('<ph_project_token>', {
    api_host: 'https://us.i.posthog.com',
    // Disable all flag requests in test environments
    ...(isTestEnv && {
        advanced_disable_feature_flags: true,
        // Bootstrap flags locally if your tests need them
        bootstrap: {
            featureFlags: {
                'your-flag': true,
                'another-flag': 'variant-a'
            }
        },
        // Reduce other traffic
        autocapture: false,
        disable_session_recording: true
    })
})
```

### Android

```java
// Detect test/CI environment
val isTestEnv = BuildConfig.DEBUG || System.getProperty("CI") != null
val config = PostHogConfig(apiKey = "<ph_project_token>").apply {
    if (isTestEnv) {
        // Disable flag requests entirely
        preloadFeatureFlags = false
        sendFeatureFlagEvent = false
        // Reduce event traffic
        flushAt = 100
        flushIntervalSeconds = 120
    }
}
PostHog.setup(context, config)
// If tests need flags to work, override them locally:
if (isTestEnv) {
    PostHog.with { it.overrideFeatureFlag("your-flag", true) }
}
```

### iOS

```objectivec
// Detect test/CI environment
let isTestEnv = ProcessInfo.processInfo.environment["CI"] != nil
let config = PostHogConfig(apiKey: "<ph_project_token>")
if isTestEnv {
    config.preloadFeatureFlags = false
    config.sendFeatureFlagEvent = false
    config.flushAt = 100
    config.flushIntervalSeconds = 120
}
PostHogSDK.shared.setup(config)
// Override flags locally if needed
if isTestEnv {
    PostHogSDK.shared.overrideFeatureFlag("your-flag", value: true)
}
```

### Dart

```dart
// Detect test/CI environment
final isTestEnv = Platform.environment['CI'] != null;
final config = PostHogConfig('<ph_project_token>');
if (isTestEnv) {
    config.preloadFeatureFlags = false;
    config.sendFeatureFlagEvents = false;
    config.flushAt = 100;
    config.flushInterval = const Duration(seconds: 120);
}
await Posthog().setup(config);
// Override flags locally if needed
if (isTestEnv) {
    await Posthog().overrideFeatureFlag('your-flag', true);
}
```

### React Native

```jsx
// Detect test/CI environment
const isTestEnv = process.env.NODE_ENV === 'test' || process.env.CI === 'true'
const posthog = await Posthog.initAsync('<ph_project_token>', {
    host: 'https://us.i.posthog.com',
    ...(isTestEnv && {
        preloadFeatureFlags: false,
        sendFeatureFlagEvent: false,
        flushAt: 100,
        flushInterval: 120000
    })
})
// Override flags locally if needed
if (isTestEnv) {
    posthog.overrideFeatureFlag('your-flag', true)
}
```

For staging environments where you want flags to work but don't need real-time evaluation, consider using [flag overrides and bootstrapping](/docs/feature-flags/testing.md#method-2-use-posthogfeatureflagsoverridefeatureflags) instead of fetching from the server.

## Audit environments to catch unexpected `/flags` calls

**Watch out for forgotten environments**

Forgotten environments, like old demos, test apps, or staging servers, and other PostHog SDKs, can silently rack up costs. Even if they don't send events, they may still be polling and making `/flags` calls for feature flags, session replay, and surveys.

These environments often get overlooked, especially if they're not part of your main deployment flow.

To identify where PostHog is still running:

1.  Create a [trends insight](https://us.posthog.com/insights/new#insight=TRENDS)
2.  Choose a high-signal event that occurs across SDKs: like `$feature_flag_called`, `$identify`, or a common custom event
3.  Set the chart type to **Table**
4.  Breakdown by `$lib`, `$lib_version`, and experiment with breakdowns on `$host`, `$device_name`, and `$app_version` as these can clue you into what environments are running PostHog
5.  Set the time range to **Last 30 days**

This shows you every SDK, domain, and version currently making requests, so you can spot any forgotten environments.

If you find environments that don't need feature flags, replays, or surveys, disable them entirely using `advanced_disable_feature_flags`:

JavaScript

PostHog AI

```javascript
posthog.init('<ph_project_token>', {
  advanced_disable_feature_flags: true // Disables feature flags, surveys, and session recording
})
```

See [advanced configuration options](/docs/libraries/js/config.md#advanced-configuration) for details.

### Community questions

Ask a question

### Was this page useful?

HelpfulCould be better