Amba
SDKsFeatures

Feature flags

Server-controlled boolean and variant flags with attached JSON payload, plus hooks for React.

Feature flags let you ship code dark and turn it on without a deploy. Today each flag has a global on/off and a global default variant — every user who fetches the flag set sees the same answer. Per-user targeting (segment-scoped flags, sticky A/B variants) is on the roadmap; the SDK contract is already shaped for it so client code won't need to change when it lands.

Two flag types:

  • Boolean flags — global on / off. Read with useFlag(name) or flags.fetch().then(...).
  • Variant flags — return a string (e.g. 'control' / 'short' / 'long'). Today the variant is the same for every user, so use these for kill-switched rollouts and config-as-flag, not as A/B traffic splitters yet.

Each flag can also carry a payload JSON value — useful for config-driven feature gates ({ "max_items": 25 }).

Coming next: per-user targeting

Segment-scoped flags and sticky A/B variants are tracked work. Until they ship, treat flags as global kill-switches plus attached config — not as A/B traffic splitters. If you ship a multi-variant flag today, every user gets the same arm.

Quick start

import { Amba } from '@layers/amba-web';
 
const flags = await Amba.flags.fetch();
const newUi = flags.find((f) => f.name === 'new_ui')?.enabled ?? false;
const copy = flags.find((f) => f.name === 'signup_copy')?.variant ?? 'control';

Operations

fetch()

Returns the full flag set for this project. Each entry has the shape:

{
  name: 'new_onboarding',     // flag name
  enabled: true,               // boolean on/off (global)
  variant: 'short',            // optional variant tag (global)
  payload: { max_items: 25 },  // optional, attached JSON config
}

Every fetch() call sends a request to the server. Hold the result yourself when you read flags often, as in the module-scope pattern below.

Today every signed-in user receives the same project-wide set of enabled flags. When per-user targeting ships, the response shape stays the same and the values start varying by user.

React hooks

useFlag(name) returns the flag's assignment ({ name, enabled, variant, payload }), or null when the flag is unknown, disabled, or still loading; read useFlag(name)?.enabled ?? false for a boolean. useVariant(name) returns the variant string (or null if unset / not loaded).

import { useFlag, useVariant } from '@layers/amba-react';
 
function NewFeature() {
  const enabled = useFlag('new_feature')?.enabled ?? false;
  const layout = useVariant('new_feature_layout'); // 'horizontal' | 'vertical' | null
  if (!enabled) return null;
 
  return layout === 'horizontal' ? <Horizontal /> : <Vertical />;
}

Both hooks return null while the initial fetch is in flight, so the ?.enabled ?? false read renders the gated UI hidden until flags load.

Patterns

Gate render at module scope

For flags that gate large slabs of the UI, prefer fetching once at app start and stashing the result:

let flagsCache: Awaited<ReturnType<typeof Amba.flags.fetch>> | null = null;
 
export async function getFlag(name: string): Promise<boolean> {
  flagsCache ??= await Amba.flags.fetch();
  return flagsCache.find((f) => f.name === name)?.enabled ?? false;
}

Config-driven gates

For A/B tests with parameterized behavior, attach a payload to the variant in the console and read it on the client:

async function loadPosts() {
  const flags = await Amba.flags.fetch();
  const flag = flags.find((f) => f.name === 'pagination_size');
  const limit = (flag?.payload as { limit?: number })?.limit ?? 20;
  const { data } = await Amba.collections.find('posts', { limit });
  return data;
}

Server-side parity

Server functions read the same flag set from the client endpoint GET /v1/client/flags, authenticated with your server key as X-Api-Key plus X-On-Behalf-Of: <user id>. Store the server key as a function secret under your own name (amba secrets set APP_SERVER_KEY <server key> --function <fn>); names starting with AMBA_ are reserved for values the platform injects. Useful for gating server logic on the same answer as the UI:

// Amba functions are ES modules with the `{ async fetch(req, env, ctx) }`
// shape; bindings come in via `env`. The X-Amba-User-Id header carries
// the calling user's id, signed by the edge.
export default {
  async fetch(req, env, ctx) {
    // Trust X-Amba-User-Id only after the edge signature checks out:
    // verifyEdgeHeaders is the verifier from Signed-header identity in
    // the functions runtime docs (/functions/runtime#signed-header-identity).
    if (!(await verifyEdgeHeaders(req, env.EDGE_HEADER_SIGNING_SECRET))) {
      return Response.json({ error: 'unsigned_request' }, { status: 401 });
    }
    const userId = req.headers.get('X-Amba-User-Id');
    if (!userId) return Response.json({ error: 'sign_in_required' }, { status: 401 });
 
    const res = await fetch(`${env.AMBA_API_URL}/v1/client/flags`, {
      headers: { 'X-Api-Key': env.APP_SERVER_KEY, 'X-On-Behalf-Of': userId },
    });
    if (!res.ok) return Response.json({ error: 'flags_unavailable' }, { status: 503 });
 
    const { data: flags } = await res.json();
    const newPipeline = flags.find((f) => f.name === 'new_processing_pipeline')?.enabled ?? false;
    return newPipeline ? newImpl() : oldImpl();
  },
};

Limits

  • Flag count: up to 100 flags per project visible to a single fetch.
  • Variant count: up to 8 variants per multi-variant flag (control + 7 treatments is a common ceiling).
  • Payload size: up to 8 KB JSON per flag.
  • Caching: none in the SDK; each fetch() is a request.

Reference

On this page