{
  "slug": "modules/feature-flags",
  "title": "Feature Flags module",
  "description": "Control-plane feature flags with groups, self-overrides, permissions, and SDK-side local evaluation.",
  "url": "https://cuitty.com/docs/modules/feature-flags",
  "markdown_url": "https://cuitty.com/docs/modules/feature-flags.md",
  "json_url": "https://cuitty.com/docs/modules/feature-flags.json",
  "frontmatter": {
    "title": "Feature Flags module",
    "description": "Control-plane feature flags with groups, self-overrides, permissions, and SDK-side local evaluation.",
    "order": 11,
    "section": "Modules",
    "updatedAt": "2026-05-28"
  },
  "headings": [
    {
      "depth": 1,
      "slug": "feature-flags-module",
      "text": "Feature Flags module"
    },
    {
      "depth": 2,
      "slug": "v2-model",
      "text": "V2 model"
    },
    {
      "depth": 2,
      "slug": "evaluation",
      "text": "Evaluation"
    },
    {
      "depth": 2,
      "slug": "sdk-examples",
      "text": "SDK examples"
    },
    {
      "depth": 2,
      "slug": "api",
      "text": "API"
    }
  ],
  "body_markdown": "# Feature Flags module\n\nThe Feature Flags module is a control-plane module for rollout decisions, not just an SDK utility. It manages flag definitions, group-level rollout state, cohort gates, kill switches, owner/developer self-overrides, and transactional history from the same permissioned surface as the rest of Cuitty.\n\n## V2 model\n\n- Flags remain stable keys such as `new_checkout` or `admin_panel`.\n- Groups collect related flags, so an operator can disable or kill every flag in a rollout area without editing each flag.\n- Group kill and flag kill are emergency stops and always evaluate false.\n- Owner/developer self-overrides can opt one actor in or out without changing the default rollout.\n- Route-level checks use the Feature Flags permission vocabulary and are designed to sit behind SpiceDB-backed project permissions.\n- Local and staging environments can inspect and toggle flags from the Cuitty toolbar once the toolbar is installed.\n\n## Evaluation\n\nSDK helpers evaluate locally using the same rule as the server and CUI preview:\n\n1. Killed or disabled groups return false.\n2. Killed flags return false.\n3. Active self-overrides return their stored value.\n4. Disabled flags return false.\n5. Non-empty cohort dimensions must match the evaluation context.\n6. 100% rollouts return true, 0% rollouts return false.\n7. Percentage rollouts use SHA-256 of `flagKey:userId`, bucketed into 0-99.\n\nSelf-overrides do not bypass a group kill switch.\n\n## SDK examples\n\nTypeScript:\n\n```ts\nconst flags = createFlagsPlugin();\ncuitty.use(flags);\n\nconst enabled = flags.isEnabled(\"new_checkout\", {\n  userId: \"user_123\",\n  env: \"staging\",\n  role: \"developer\",\n});\n```\n\nPython:\n\n```python\nenabled = client.flags.is_enabled(\n    \"new_checkout\",\n    user_id=\"user_123\",\n    env=\"staging\",\n    role=\"developer\",\n)\n```\n\nGo:\n\n```go\nenabled, err := client.Flags.IsEnabled(ctx, \"new_checkout\", cuitty.EvalContext{\n    \"userId\": \"user_123\",\n    \"env\": \"staging\",\n    \"role\": \"developer\",\n})\n```\n\nRust:\n\n```rust\nlet mut ctx = cuitty::plugins::flags::EvalContext::new();\nctx.insert(\"userId\".into(), \"user_123\".into());\nctx.insert(\"env\".into(), \"staging\".into());\nctx.insert(\"role\".into(), \"developer\".into());\n\nlet enabled = client.flags().is_enabled(\"new_checkout\", &ctx).await?;\n```\n\n## API\n\n| Endpoint | Returns |\n| --- | --- |\n| `GET /api/flags/status` | Module health |\n| `GET /api/flags?project_id=...` | Flags for a project |\n| `GET /api/flags/{key}?project_id=...` | A single flag |\n| `GET /api/flags/groups?project_id=...` | Groups for a project |\n| `POST /api/flags/groups/{key}/kill` | Group emergency kill |\n| `PUT /api/flags/{key}/self-override` | Set the caller's self-override |\n| `GET /api/flags/stream?project_id=...` | Server-sent update stream |\n\nProduct page: [Feature Flags](/product/modules/feature-flags).",
  "body_html": "<h1 id=\"feature-flags-module\">Feature Flags module</h1>\n<p>The Feature Flags module is a control-plane module for rollout decisions, not just an SDK utility. It manages flag definitions, group-level rollout state, cohort gates, kill switches, owner/developer self-overrides, and transactional history from the same permissioned surface as the rest of Cuitty.</p>\n<h2 id=\"v2-model\">V2 model</h2>\n<ul>\n<li>Flags remain stable keys such as <code>new_checkout</code> or <code>admin_panel</code>.</li>\n<li>Groups collect related flags, so an operator can disable or kill every flag in a rollout area without editing each flag.</li>\n<li>Group kill and flag kill are emergency stops and always evaluate false.</li>\n<li>Owner/developer self-overrides can opt one actor in or out without changing the default rollout.</li>\n<li>Route-level checks use the Feature Flags permission vocabulary and are designed to sit behind SpiceDB-backed project permissions.</li>\n<li>Local and staging environments can inspect and toggle flags from the Cuitty toolbar once the toolbar is installed.</li>\n</ul>\n<h2 id=\"evaluation\">Evaluation</h2>\n<p>SDK helpers evaluate locally using the same rule as the server and CUI preview:</p>\n<ol>\n<li>Killed or disabled groups return false.</li>\n<li>Killed flags return false.</li>\n<li>Active self-overrides return their stored value.</li>\n<li>Disabled flags return false.</li>\n<li>Non-empty cohort dimensions must match the evaluation context.</li>\n<li>100% rollouts return true, 0% rollouts return false.</li>\n<li>Percentage rollouts use SHA-256 of <code>flagKey:userId</code>, bucketed into 0-99.</li>\n</ol>\n<p>Self-overrides do not bypass a group kill switch.</p>\n<h2 id=\"sdk-examples\">SDK examples</h2>\n<p>TypeScript:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"ts\"><code><span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#79B8FF\"> flags</span><span style=\"color:#F97583\"> =</span><span style=\"color:#B392F0\"> createFlagsPlugin</span><span style=\"color:#E1E4E8\">();</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">cuitty.</span><span style=\"color:#B392F0\">use</span><span style=\"color:#E1E4E8\">(flags);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#79B8FF\"> enabled</span><span style=\"color:#F97583\"> =</span><span style=\"color:#E1E4E8\"> flags.</span><span style=\"color:#B392F0\">isEnabled</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"new_checkout\"</span><span style=\"color:#E1E4E8\">, {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  userId: </span><span style=\"color:#9ECBFF\">\"user_123\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  env: </span><span style=\"color:#9ECBFF\">\"staging\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  role: </span><span style=\"color:#9ECBFF\">\"developer\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">});</span></span></code></pre>\n<p>Python:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"python\"><code><span class=\"line\"><span style=\"color:#E1E4E8\">enabled </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> client.flags.is_enabled(</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\">    \"new_checkout\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\">    user_id</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">\"user_123\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\">    env</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">\"staging\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\">    role</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">\"developer\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">)</span></span></code></pre>\n<p>Go:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"go\"><code><span class=\"line\"><span style=\"color:#E1E4E8\">enabled, err </span><span style=\"color:#F97583\">:=</span><span style=\"color:#E1E4E8\"> client.Flags.</span><span style=\"color:#B392F0\">IsEnabled</span><span style=\"color:#E1E4E8\">(ctx, </span><span style=\"color:#9ECBFF\">\"new_checkout\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#B392F0\">cuitty</span><span style=\"color:#E1E4E8\">.</span><span style=\"color:#B392F0\">EvalContext</span><span style=\"color:#E1E4E8\">{</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\">    \"userId\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"user_123\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\">    \"env\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"staging\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\">    \"role\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"developer\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">})</span></span></code></pre>\n<p>Rust:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"rust\"><code><span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#F97583\"> mut</span><span style=\"color:#E1E4E8\"> ctx </span><span style=\"color:#F97583\">=</span><span style=\"color:#B392F0\"> cuitty</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">plugins</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">flags</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">EvalContext</span><span style=\"color:#F97583\">::</span><span style=\"color:#B392F0\">new</span><span style=\"color:#E1E4E8\">();</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">ctx</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">insert</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"userId\"</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">into</span><span style=\"color:#E1E4E8\">(), </span><span style=\"color:#9ECBFF\">\"user_123\"</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">into</span><span style=\"color:#E1E4E8\">());</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">ctx</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">insert</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"env\"</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">into</span><span style=\"color:#E1E4E8\">(), </span><span style=\"color:#9ECBFF\">\"staging\"</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">into</span><span style=\"color:#E1E4E8\">());</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">ctx</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">insert</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"role\"</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">into</span><span style=\"color:#E1E4E8\">(), </span><span style=\"color:#9ECBFF\">\"developer\"</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">into</span><span style=\"color:#E1E4E8\">());</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> enabled </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> client</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">flags</span><span style=\"color:#E1E4E8\">()</span><span style=\"color:#F97583\">.</span><span style=\"color:#B392F0\">is_enabled</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"new_checkout\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#F97583\">&#x26;</span><span style=\"color:#E1E4E8\">ctx)</span><span style=\"color:#F97583\">.await?</span><span style=\"color:#E1E4E8\">;</span></span></code></pre>\n<h2 id=\"api\">API</h2>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<table><thead><tr><th>Endpoint</th><th>Returns</th></tr></thead><tbody><tr><td><code>GET /api/flags/status</code></td><td>Module health</td></tr><tr><td><code>GET /api/flags?project_id=...</code></td><td>Flags for a project</td></tr><tr><td><code>GET /api/flags/{key}?project_id=...</code></td><td>A single flag</td></tr><tr><td><code>GET /api/flags/groups?project_id=...</code></td><td>Groups for a project</td></tr><tr><td><code>POST /api/flags/groups/{key}/kill</code></td><td>Group emergency kill</td></tr><tr><td><code>PUT /api/flags/{key}/self-override</code></td><td>Set the caller’s self-override</td></tr><tr><td><code>GET /api/flags/stream?project_id=...</code></td><td>Server-sent update stream</td></tr></tbody></table>\n<p>Product page: <a href=\"/product/modules/feature-flags\">Feature Flags</a>.</p>",
  "links_out": [
    "/product/modules/feature-flags"
  ]
}