Skip to content

Latest commit

 

History

History

README.md

@cloudflare/flagship

npm version npm downloads license

OpenFeature-compliant provider SDK for Flagship, Cloudflare's globally distributed, low-latency feature flag platform.

Server-side (Node.js, Cloudflare Workers) and client-side (browser) support via isolated sub-path exports. Tree-shakeable — importing @cloudflare/flagship/server never loads @openfeature/web-sdk and vice versa.

Install

Server-side (Node.js, Cloudflare Workers):

npm install @cloudflare/flagship @openfeature/server-sdk

Client-side (browser):

npm install @cloudflare/flagship @openfeature/web-sdk

Quick start — server

import { OpenFeature } from '@openfeature/server-sdk';
import { FlagshipServerProvider } from '@cloudflare/flagship/server';

await OpenFeature.setProviderAndWait(
  new FlagshipServerProvider({ appId: 'your-app-id', accountId: 'your-account-id', authToken: 'your-token' }),
);

const client = OpenFeature.getClient();
const enabled = await client.getBooleanValue('dark-mode', false, { userId: 'user-123' });

Quick start — Cloudflare Workers

The recommended approach for Cloudflare Workers. Uses a wrangler binding — no HTTP overhead, no auth tokens needed.

Configure in wrangler.json:

{
  "flagship": [{ "binding": "FLAGS", "app_id": "<your-app-id>" }]
}
import { OpenFeature } from '@openfeature/server-sdk';
import { FlagshipServerProvider } from '@cloudflare/flagship/server';
import type { FlagshipBinding } from '@cloudflare/flagship/server';

export default {
  async fetch(request: Request, env: { FLAGS: FlagshipBinding }): Promise<Response> {
    await OpenFeature.setProviderAndWait(new FlagshipServerProvider({ binding: env.FLAGS }));

    const client = OpenFeature.getClient();
    const darkMode = await client.getBooleanValue('dark-mode', false, {
      targetingKey: new URL(request.url).searchParams.get('userId') ?? 'anonymous',
    });

    return Response.json({ darkMode });
  },
};

Quick start — HTTP

For Workers without a Flagship binding, or non-Workers server environments:

import { OpenFeature } from '@openfeature/server-sdk';
import { FlagshipServerProvider } from '@cloudflare/flagship/server';

let initialized = false;

export default {
  async fetch(request: Request): Promise<Response> {
    if (!initialized) {
      await OpenFeature.setProviderAndWait(
        new FlagshipServerProvider({ appId: 'your-app-id', accountId: 'your-account-id', authToken: 'your-token' }),
      );
      initialized = true;
    }

    const client = OpenFeature.getClient();
    const darkMode = await client.getBooleanValue('dark-mode', false, {
      userId: new URL(request.url).searchParams.get('userId') ?? 'anonymous',
    });

    return Response.json({ darkMode });
  },
};

Caching

The server provider can cache evaluations to avoid a network round-trip (HTTP mode) or binding call (binding mode) for repeated flag/context pairs. Caching is off by default and enabled by setting cacheTtl:

new FlagshipServerProvider({
  appId: 'your-app-id',
  accountId: 'your-account-id',
  cacheTtl: 30_000, // ms — enables caching; values may be up to this stale
  cacheMaxSize: 1000, // max cached entries, LRU-evicted (default: 1000)
});

Each entry is keyed by flag key, type, and the full evaluation context, so distinct contexts never share a value. Cache hits resolve with reason: 'CACHED'. Disabled flags and errors are never cached. Because freshness is TTL-based, a flag change in Flagship takes effect after the entry expires.

Quick start — browser

import { OpenFeature } from '@openfeature/web-sdk';
import { FlagshipClientProvider } from '@cloudflare/flagship/web';

await OpenFeature.setProviderAndWait(
  new FlagshipClientProvider({
    appId: 'your-app-id',
    accountId: 'your-account-id',
    authToken: 'your-token',
    prefetchFlags: ['dark-mode', 'welcome-message'],
  }),
);

await OpenFeature.setContext({ userId: 'user-123', plan: 'premium' });

const client = OpenFeature.getClient();
const darkMode = client.getBooleanValue('dark-mode', false);

Features

Feature Description
OpenFeature compliant Implements the CNCF OpenFeature specification
Workers binding Native wrangler binding support — zero HTTP overhead, no auth tokens
Server + client Async per-request (server) and sync cache-based (browser) providers
Server providers FlagshipServerProvider works via HTTP or wrangler binding
All flag types Boolean, string, number, and object (JSON)
Authentication authToken option adds Authorization: Bearer to every request (HTTP only)
Logging logging option surfaces fetch errors and cache misses (off by default)
Response caching Opt-in per-context TTL + LRU cache via cacheTtl (off by default)
Retries + timeouts Configurable retry logic with AbortController-based timeouts (HTTP only)
Custom transport Inject fetch per client or per call — no global mutation (HTTP only)
Cancellation Caller AbortSignal aborts the in-flight request, never retried (HTTP only)
Hooks Built-in LoggingHook and TelemetryHook
Tree-shakeable Server and client bundles are fully isolated
TypeScript Strict types throughout

Packages

Export Description Peer dependency
@cloudflare/flagship Core client, types, errors None
@cloudflare/flagship/server FlagshipServerProvider + hooks @openfeature/server-sdk
@cloudflare/flagship/web FlagshipClientProvider @openfeature/web-sdk

Documentation

Development

pnpm install         # install dependencies
pnpm run dev         # watch mode
pnpm run test        # run tests
pnpm run build       # build for distribution

Contributing

Contributions are welcome. Please open an issue first to discuss what you'd like to change. See the repository for more details.

License

Apache-2.0