Feature flags & experiments

Guidely ships a full feature-flag, remote-config and A/B-testing system alongside guides and analytics. It covers what GrowthBook and ConfigCat offer — including the features they reserve for paid licences — and plugs into the key you already use for tracking: the environment public key (pk_<env>_…) works in browsers, mobile apps and servers with no extra setup.

  • Dashboard: Release → Feature flags and Release → Experiments.
  • SDKs: React, Next.js (client + server), React Native, native iOS (Swift/SwiftUI), native Android (Kotlin/Compose), Svelte/SvelteKit, TypeScript/JavaScript (browser, Node, edge), PHP, Java.
  • Every SDK follows the same evaluation rules, so a user gets the same value on every platform.

Concepts

ConceptWhat it is
FlagA key (new-checkout) with a type — boolean, string, number or JSON — and per-environment serving rules. Types make flags double as remote config.
EnvironmentDevelopment, staging, production. Each has its own on/off switch, default value and rule list, and its own public key.
Off valueWhat a flag serves when it is switched off in an environment or a prerequisite fails.
Default valueWhat an enabled flag serves when no rule serves a value.
RuleOrdered; the first rule that serves wins. Force (targeted value), Percentage rollout (a share of matching users; the rest fall through), Percentage split (percentage options: everyone matching gets one of several values), Experiment (variations with weights, tracked exposures, namespaces, sticky bucketing). Rules can be scheduled (start/end) and rollouts can ramp on a schedule.
ConditionAttribute comparisons (40+ operators: text, regex, numbers, SemVer, dates, arrays, booleans, presence, confidential/hashed), segment membership, other flags' values, and nested AND / OR / NOT groups.
AttributeWhat you know about the user/request (id, email, plan, account.id, appVersion, …). Declare custom attributes with a type; mark confidential ones secure so their comparison values are hashed before they reach browsers. The Guidely browser, React Native, iOS and Android SDKs fill many automatically (user id, traits, account, audiences, URL, device, locale).
SegmentA reusable condition set, or an ID list (optionally hashed), referenced from any rule.
PrerequisiteA flag that must be on (or equal a value) for another to serve anything but its off value; also usable inside rule conditions. Cycles are rejected at save time.
ExperimentA rule that splits users into variations and records an experiment_viewed exposure per user. Results compare goal events (conversion or count) per variation with uplift, intervals, Bayesian chance to win and a sample-ratio-mismatch check. Stopping an experiment with a winner rolls the winner out.
NamespaceA 0–100% space that keeps experiments mutually exclusive: each experiment claims a range.
HoldoutA share of users held back from a set of flags (they get default values) to measure the combined impact of shipped features.
RevisionEvery change to what a flag serves is a numbered revision with author, comment and diff. Revert by publishing an older revision again.

Governance

  • Approvals — choose environments (typically production) where changes need review. Saving creates a pending review revision; someone with Approve flag changes approves or requests changes; then it is published. Self-approval is off unless enabled. Turning a flag off is always an immediate kill switch.
  • Required comments — make every change carry a note.
  • Permissions — Edit feature flags (editors), Change production flags, Approve flag changes, Manage flag settings (admins by default), adjustable per member.
  • Audit log — every change, approval, kill switch and settings update.
  • Custom fields — project-defined metadata (text, number, enum, URL, date, user…) on every flag.
  • JSON schema — validate JSON flag values on save.
  • Stale flags — temporary flags with no evaluations, or serving a single value to everyone, for a configurable number of days are marked stale with the reason.
  • Code references — npx guidely-flags code-refs --key gly_sk_… --repo app --branch main in CI uploads where each flag key appears in your code; the flag page lists them.
  • Webhooks — signed (X-Guidely-Signature: sha256=…) POSTs when flags change, are killed, or an experiment stops.

Workspace flags

A flag or segment belongs to one project, or to the workspace. A workspace flag applies to every project in the workspace (including ones created later) or to a chosen list of projects. Its rules for development, staging and production apply to that environment in each of those projects, so every project's SDK payload carries it unchanged. Rollouts are seeded by the flag key, so a user gets the same value in every app. Typical uses: a maintenance banner, a shared kill switch, a feature that spans a web app and a mobile app.

  • Keys are unique across the workspace: a workspace flag or segment can't reuse a key any project uses, and vice versa (two projects may still share a project flag key).
  • References — workspace flags and segments use only workspace segments and workspace flags. Project flags may use workspace segments and depend on workspace flags. Workspace flags can't hold experiment rules: experiments run per project.
  • Governance — workspace flags follow the workspace's flag settings (approvals, required comments, staleness, custom fields) and the same permissions as project flags. Usage, staleness and code references add up across the projects the flag applies to.
  • Moving — Move to workspace turns a project flag into a workspace flag (not while it has experiment rules or uses the project's segments); Move to project does the reverse (not while flags or segments of other projects use it). Each move is a published revision.
  • Payload versions — publishing, toggling, archiving or moving a workspace flag, editing a workspace segment, or changing which projects a flag applies to bumps the payload version of every affected project, and their webhooks fire.

Delivery & security

  • SDKs download one JSON payload per environment (ETag-cached) and evaluate locally — no per-flag network calls, works offline from the last payload. Streaming (SSE) pushes changes in seconds; polling is the default fallback.
  • Remote evaluation — set an environment's exposure to remote and browsers/apps never see rules: they send attributes and receive values. Server SDKs then use a secret gly_sk_ key with the flags:read scope (Settings → API keys).
  • Server-only flags are never sent to public keys.
  • Encrypted payloads (AES-256-GCM) for public keys; SDKs take the decryption key.
  • Confidential operators hash comparison values with a per-environment salt, so e-mail lists and other sensitive targeting values don't leak through the browser.
  • Sticky bucketing keeps users in their variation when weights change; bump the bucket version to re-randomise and set a minimum version to keep old participants out.

Capabilities

Everything below is included; nothing is gated behind a plan. (Several of these — approvals, audit logs, holdouts, code references, encrypted payloads, scheduling, sticky bucketing, custom fields, JSON validation — are paid-tier features in other flag tools.)

AreaCapabilities
FlagsBoolean, string, number and JSON values; predefined variations; off value; per-environment kill switch and default; tags, owner, temporary/permanent lifetime; server-only flags; archive, duplicate
TargetingOrdered rules; 40+ operators (text, regex, numbers, SemVer, dates, arrays, booleans, presence, type, confidential/hashed); nested AND/OR/NOT; segments (conditions or ID lists, hashed lists); prerequisite flags and flag conditions; Guidely audiences
RolloutsPercentage rollouts on any hash attribute; percentage splits; scheduled rules; scheduled ramps; holdouts
ExperimentsWeighted variations, coverage, hash + fallback attribute, namespaces, sticky bucketing with bucket versions, exposure tracking, conversion/count metrics, uplift with intervals, chance to win, SRM check, stop-and-roll-out-winner
GovernanceRevisions with diff and revert, approvals per environment, required comments, granular permissions, audit log, custom fields, JSON schema validation, stale-flag detection, code references, signed webhooks
DeliveryETag-cached payloads, streaming (SSE), polling modes (auto, lazy, manual), bootstrap/SSR payloads, offline cache, remote evaluation, encrypted payloads, local, URL and dev overrides (CI), evaluation details, hooks, usage reporting
SDKsReact, Next.js (client + server), React Native, iOS, Android, Svelte/SvelteKit, TypeScript/JavaScript (browser, Node, edge), PHP, Java — all evaluating flags identically

Using flags

Full install guides per framework are in sdk.md; each SDK's README has the complete option list. The common shape:

// Browser — already using Guidely? flags come with it.
guidely.isFeatureEnabled("new-checkout");
guidely.getFeatureValue("checkout-button-color", "blue");

// React
const enabled = useFlag("new-checkout");
const color = useFeatureValue("checkout-button-color", "blue");

// Node / Next.js server — one client per process, one bound evaluator per request
import { createFlagsClient } from "@guidelytech/flags";
const flags = createFlagsClient({ sdkKey: process.env.GUIDELY_PROJECT_ID! });
await flags.ready();
flags.forUser({ id: user.id, plan: user.plan }).isEnabled("new-checkout");
$flags = new \GuidelyTech\Flags\GuidelyFlags(['sdkKey' => getenv('GUIDELY_PROJECT_ID')]);
$flags->isEnabled('new-checkout', ['id' => $user->id, 'plan' => $user->plan]);
// iOS — flags come with the Guidely SDK you already initialise
if Guidely.isFeatureEnabled("new-checkout") { … }
@FeatureFlag("new-checkout") var newCheckout   // SwiftUI, updates the view on change
// Android
if (Guidely.isFeatureEnabled("new-checkout")) { … }
val newCheckout = rememberFeatureFlag("new-checkout")  // Jetpack Compose
GuidelyFlags flags = GuidelyFlags.builder(System.getenv("GUIDELY_PROJECT_ID")).build();
flags.forUser(Map.of("id", user.getId())).isEnabled("new-checkout");

Hash attribute: rollouts, splits and experiments bucket on id by default. The Guidely browser and mobile SDKs set id to the identified user id (or the anonymous id before identify). On the server pass the same id so a user gets the same value everywhere.

Testing with flags in CI

Every SDK reads dev overrides from outside the code, so a pipeline can switch flags without changing the app:

# Pin values; localOnly also stops all flag network traffic and ignores cached payloads.
export GUIDELY_FLAG_OVERRIDES='new-checkout:true,checkout-button-color:"green"'
export GUIDELY_FLAG_OVERRIDE_BEHAVIOUR=localOnly
# Or keep them in a file: a JSON object, or one key:value per line.
export GUIDELY_FLAG_OVERRIDES_FILE=ci/flags.json

A sample ci/flags.json:

{
  "new-checkout": true,
  "checkout-button-color": "green",
  "max-cart-items": 25,
  "pricing-page": { "layout": "grid", "plans": ["starter", "pro", "team"] },
  "holiday-banner": null
}

Each key is a flag key and each value is served as that flag's value (reason OVERRIDE), whatever the flag's rules say and even when the flag is switched off. Keys that are not flags in the environment are served too, so a test can cover a flag before it is created. Give each value the flag's type: isEnabled only reads booleans, and getNumber only reads numbers; a value of the wrong type, or null as with holiday-banner, makes the getter return the fallback passed in code. The same file can instead hold one key:value per line, e.g. new-checkout:true and checkout-button-color:"green". Values are read as JSON, and as plain text when they are not valid JSON.

WhereHow the values get in
Node, Next.js / SvelteKit server, PHP, Javathe environment variables above
Browser (Playwright, Cypress)window.__GUIDELY_FLAG_OVERRIDES__ = { … } in an init script, or ?guidely_flags= with enableUrlOverrides
iOS (XCUITest, simulator)app.launchEnvironment["GUIDELY_FLAG_OVERRIDES"], or SIMCTL_CHILD_GUIDELY_FLAG_OVERRIDES=… xcrun simctl launch
Androiddebuggable builds read /sdcard/Android/data/<package>/files/guidely-flag-overrides.json (adb push); JVM tests read the variables; instrumented tests can call setOverrides

Dev overrides win over overrides set in code, are served with reason OVERRIDE, and are never counted in usage. Each SDK logs one warning when they are active, so a variable left set in production is visible. Turn them off with the devOverrides: false option.

Experiments end to end

  1. Create the experiment (Release → Experiments) with goal metrics — events you already track (guidely.track("checkout_completed")) or built-ins like page_view.
  2. Add an Experiment rule to a flag and publish.
  3. Exposures are recorded automatically: the Guidely browser/mobile SDKs send experiment_viewed events with the session; server SDKs post exposures to /api/v1/sdk/flags/events (or call your own trackingCallback).
  4. Watch results; stop with a winner to roll it out to everyone the rule targets.

Migrating from ConfigCat

Feature flags → Import from ConfigCat copies flags, targeting rules and segments from ConfigCat into the current project (needs Edit feature flags; importing into production also needs Change production flags).

  1. In ConfigCat, create a credential under My account → Public API credentials and enter its basic auth user name and password. Guidely uses it only for the requests the dialog makes and never stores it.
  2. Pick the product, the configs (config V1 and V2 are both supported) and, for each Guidely environment, the ConfigCat environment it takes its values from. Environments you leave off get the flags switched off.
  3. Review the preview: every flag with its new key, value type, rule count per environment and status (New, Already here, Key clash, Can't import), the segments and custom attributes that will be created, and notes about behaviour that changes. Nothing is written until you import.
  4. Import. Each flag is created as published version 1 (with a flag.import audit entry). Run the import again later to bring over flags added in ConfigCat since: flags already in the project are skipped and segments from an earlier import are reused.

How ConfigCat maps onto Guidely:

ConfigCatGuidely
Feature flag / setting (bool, text, whole, decimal number)Project flag (boolean, string, number); a text setting marked as JSON becomes a json flag when all its values parse
Default value per environmentEnvironment default value, flag on
Targeting rule with AND conditionsforce rule with the same conditions
Percentage options (on a rule or the whole flag)split rule (bucketing on the flag's percentage attribute)
User conditionAttribute condition (IS ONE OF → in, CONTAINS ANY OF → contains, SemVer, number and date comparators → semver*, gt/lt…, before/after; confidential comparators → hashed operators)
SegmentConditions segment (only segments the imported flags use); IS NOT IN SEGMENT also requires the segment's attribute, as in ConfigCat
Prerequisite flag conditionFlag condition (prerequisites are created first)
Predefined variationsVariations
TagsTags, plus configcat

What to change when you switch SDKs:

  • Keys are lower-case in Guidely: isNewCheckout becomes is-new-checkout. Download the key map (configcat-key-map.json, ConfigCat key → Guidely key) from the preview or the result to update your code. Two ConfigCat keys that map to the same key are reported as a key clash; only the first is imported.
  • Attributes: ConfigCat's Identifier, Email and Country become id, email and country; custom attributes keep their names (and are added to the attribute catalogue).
  • Percentage options bucket users with Guidely's hash, so a different set of users lands in each percentage once your app reads flags from Guidely. Finish or settle percentage rollouts in ConfigCat first where that matters.
  • Dates: send date attributes as Unix seconds (a number) or a date object. ConfigCat also accepted numeric strings; Guidely reads strings as ISO 8601 dates.
  • Empty strings: ConfigCat treats an empty attribute as missing; Guidely treats it as a value, so negative comparators (notIn, notContains…) can match it.

A safe cut-over: import with production mapped, read flags through a thin wrapper that evaluates both ConfigCat and Guidely and logs differences (ignoring percentage splits) for a few days, then switch the wrapper to Guidely and remove the ConfigCat SDK.