Skip to main content

Feature Flags

Ship code behind flags and control who sees it — without redeploying. Manage flags from your Simplr dashboard, deliver them through the SDK, and evaluate them locally and deterministically so there's no network call on every check. Roll out by percentage, target specific users, and let your CI journey tests gate the rollout automatically.

Overview

Feature Flags provide:

  • SDK-delivered config — the JS and Flutter SDKs fetch flags with a public key and cache them
  • Local, deterministic evaluationisEnabled() runs in-process; the same user always gets the same answer
  • Percentage rollouts & targeting — release to X% of users, hand-pick user IDs, or match attributes with rules
  • Production & sandbox — flags live in a live or test environment, keyed independently
  • Test-gated progressive rollout — link a flag to a CI journey tag; passing tests advance the rollout, a failure halts it and alerts you
  • Audit log & rollback — every change is recorded with who, what, and the from/to percentage
  • Per-request billing — 1,000,000 flag config requests per month free, then $0.03 per 1,000

Because evaluation happens locally, flag checks are instant and free — you're only billed when an SDK fetches the config (on init and refresh), not per isEnabled() call.

How rollout percentages work

A percentage rollout needs no knowledge of your total user count. Each SDK hashes a stable identifier (the user ID, or the device ID for anonymous users) together with the flag key and maps it to a bucket from 0–99:

bucket = hash(`${flagKey}:${userId}`) % 100
enabled = bucket < rollout_percentage

Because the hash is deterministic, a given user lands in the same bucket every time — so they don't flicker between on and off across sessions, and raising the percentage only ever adds users (never removes the ones already in). This is what makes blue-green and canary releases safe.

Evaluation order for isEnabled():

  1. If the flag is disabled, return false.
  2. If the user is in target_user_ids, return true.
  3. If any rule matches the supplied attributes, return true.
  4. Otherwise fall back to the percentage bucket.

Quick start

1. Create a flag

In the dashboard, go to Developer → Feature Flags → New flag. Give it a key (e.g. new-checkout), choose Production or Sandbox, and set a starting rollout percentage. You can also add targeted user IDs and a description.

2. Get a public key

Flags are read with a public key (pk_live_* / pk_test_*) — never a secret key. Create one under Developer → API Keys. Public keys can only read flag config; they can't touch any other API.

3. Install the SDK and evaluate

JavaScript

import { simplrFlags } from "@simplr-ai/js";

await simplrFlags.initialize({
apiKey: "pk_live_xxx",
environment: "live", // or "test"
refreshIntervalMs: 60000, // optional; 0 disables auto-refresh
});

simplrFlags.setUser("user_123"); // optional; falls back to device ID

if (simplrFlags.isEnabled("new-checkout")) {
// show the new checkout
}

// With per-call context (rules / overrides):
simplrFlags.isEnabled("new-checkout", {
userId: "user_123",
attributes: { plan: "growth", country: "GB" },
});

Flutter

import 'package:simplr_fraud/simplr_fraud.dart';

final flags = SimplrFlags();

await flags.initialize(
apiKey: 'pk_live_xxx',
environment: 'live', // or 'test'
refreshInterval: const Duration(minutes: 1),
);

flags.setUser('user_123'); // optional; falls back to device ID

if (flags.isEnabled('new-checkout')) {
// show the new checkout
}

// With per-call context (rules / overrides):
flags.isEnabled(
'new-checkout',
userId: 'user_123',
attributes: {'plan': 'growth', 'country': 'GB'},
);

Both SDKs fetch the config once on initialize() and again on each refresh, keeping the last-known config if a refresh fails — so a network blip never flips your flags off.

Reading flags directly (HTTP)

If you're not using an SDK, fetch the config yourself with a public key:

curl "https://api.simplr-ai.com/v1/flags?environment=live" \
-H "X-API-Key: pk_live_xxx"
{
"content": {
"environment": "live",
"flags": [
{
"key": "new-checkout",
"enabled": true,
"rollout_percentage": 40,
"target_user_ids": ["user_123"],
"rules": [{ "attribute": "plan", "op": "eq", "value": "growth" }]
}
]
}
}

Each call to this endpoint counts as one billable flag request (see Pricing). Evaluate locally with the bucket formula above — don't call this per check.

Targeting & rules

  • Targeted users — add user IDs to a flag to always-on it for specific accounts (beta testers, internal staff), regardless of the percentage.
  • Rules — match on attributes you pass to isEnabled(). Each rule is { attribute, op, value } with op of eq, neq, or contains. Any matching rule turns the flag on.
// Flag rule: { attribute: "plan", op: "eq", value: "growth" }
simplrFlags.isEnabled("new-checkout", { attributes: { plan: "growth" } }); // → true

Test-gated progressive rollout

Tie a flag's rollout to your CI journey tests so it only reaches more users while your tests are green.

  1. Tag a journey with a rollout tag (e.g. ci) in the journey Settings.
  2. On the flag, set Rollout mode to Auto and choose:
    • Gating tag — the journey tag to watch (e.g. ci)
    • Step % — how much to advance each interval (e.g. 10)
    • Max % — the ceiling to stop at (e.g. 100)
    • Interval — how often to consider advancing (minutes)

A controller runs every few minutes. For each auto flag:

  • All gating journeys passing + interval elapsed + below max → advance rollout_percentage by the step (recorded in the audit log, source controller).
  • Any gating journey failinghalt at the current percentage and send an alert to your org's billing email. The percentage is never lowered automatically.

This gives you automated progressive delivery: a code change runs your CI journeys, and only green tests let the feature reach more real users.

Run journey tests in your CI

Add this step to your own pipeline. It runs every journey tagged ci and fails the build if any of them fail — no extra script or dependency to install. The rollout controller then watches those same runs to gate the flag.

First add a CI secret SIMPLR_API_KEY (a secret sk_* key from Developer → API Keys), then:

# .github/workflows/simplr-journeys.yml
name: Simplr journey tests
on: [push, pull_request]
jobs:
journeys:
runs-on: ubuntu-latest
steps:
- name: Run Simplr journeys (fail build on any failure)
run: |
R=$(curl -sf -X POST "$SIMPLR_API_URL/v1/journeys/ci/run" \
-H "X-API-Key: $SIMPLR_API_KEY" -H "Content-Type: application/json" \
-d '{"tag":"ci"}')
echo "$R" | jq .
test "$(echo "$R" | jq '.content.failed')" -eq 0
env:
SIMPLR_API_KEY: ${{ secrets.SIMPLR_API_KEY }}
SIMPLR_API_URL: https://api.simplr-ai.com

The same pattern works in any CI (GitLab, CircleCI, Jenkins) — it's just a curl plus a check that failed == 0. Use the same tag here and in the flag's Gating tag so the rollout advances only when these tests are green.

Audit log & rollback

Every create, toggle, percentage change, halt, resume, and rollback is written to the flag's history with the actor, the from/to percentage, a reason, and whether the change was manual or from the controller. Open the Logs (📜) action on any flag to browse the full audit trail (paginated). Use Rollback (↺) to return a flag to a safe state in one click.

Environments

Each flag belongs to either the live (Production) or test (Sandbox) environment and is keyed independently, so you can validate a flag in sandbox with a pk_test_* key and promote the same key to production with a pk_live_* key. The SDK loads one environment per instance, chosen by the environment option (defaulting to the key's own environment).

Pricing

Feature Flags use shared credits per config request — 1,000,000 live requests per month free, then 6 credits per 1,000. Test-key requests and local isEnabled() checks are never billed. See Pricing → Feature Flags for details and how to tune your refresh interval.