All skills
vercel-labs avatar

/flags-sdk

@ce31fdf official

Set up and use feature flags and A/B tests with the Flags SDK (`flags` npm package) and Vercel Flags. Use when installing or configuring the SDK, adding a new or existing flag, wiring `vercelAdapter` (OIDC or SDK keys), declaring flags with `flag()`, using the `vercel flags` CLI (create, inspect, list, enable, disable, set, update, split, rollout, rules, segments, use-targeting, evaluations, versions, open, archive, unarchive, rm, sdk-keys, override, prepare), setting up providers/adapters (Vercel, Statsig, LaunchDarkly, PostHog, GrowthBook, Global Config, OpenFeature, Split, Flagsmith, Reflag, Optimizely, or custom), precompute, `identify`/`dedupe`, Flags Explorer/Toolbar, Next.js or SvelteKit, or encrypting flag values. Triggers: feature flags, feature gates, A/B testing, experimentation, gradual rollout, traffic split, targeting rules, flag overrides, precompute, Flags Explorer, Vercel Flags, vercel flags CLI, `flags/next`, `flags/sveltekit`, `flags/react`, `@flags-sdk/*`.

  • 11 files
  • 168.6 KB
  • Updated 13 hours ago
  • GitHub

Use this Skill: https://skilld.dev/gh/vercel-labs/vercel-plugin/flags-sdk

This session only. Nothing lands on disk.

referencessveltekit.md

≈2.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

SvelteKit Integration

Table of Contents

Quickstart

Installation

pnpm i flags @vercel/toolbar

Create a flag

// src/lib/flags.ts
import { flag } from 'flags/sveltekit';

export const showDashboard = flag<boolean>({
  key: 'showDashboard',
  description: 'Show the dashboard',
  origin: 'https://example.com/#showdashboard',
  options: [{ value: true }, { value: false }],
  decide(_event) {
    return false;
  },
});

Set up the server hook

One-time setup that makes the toolbar aware of your flags:

// src/hooks.server.ts
import { createHandle } from 'flags/sveltekit';
import { FLAGS_SECRET } from '$env/static/private';
import * as flags from '$lib/flags';

export const handle = createHandle({ secret: FLAGS_SECRET, flags });

When composing with other handlers via SvelteKit's sequence, createHandle must come first.

Use the flag

// src/routes/+page.server.ts
import { showDashboard } from '$lib/flags';

export const load = async () => {
  const dashboard = await showDashboard();
  return {
    post: { title: dashboard ? 'New Dashboard' : 'Old Dashboard' },
  };
};
<!-- src/routes/+page.svelte -->
<script lang="ts">
  import type { PageProps } from './$types';
  let { data }: PageProps = $props();
</script>

<h1>{data.post.title}</h1>

Toolbar Setup

  1. Install @vercel/toolbar
  2. Add vite plugin:
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { vercelToolbar } from '@vercel/toolbar/plugins/vite';

export default defineConfig({
  plugins: [sveltekit(), vercelToolbar()],
});
  1. Render toolbar in layout:
<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import type { LayoutProps } from './$types';
  import { mountVercelToolbar } from '@vercel/toolbar/vite';
  import { onMount } from 'svelte';

  onMount(() => mountVercelToolbar());

  let { children }: LayoutProps = $props();
</script>

<main>
  {@render children()}
</main>

Flag Declaration

import { flag } from 'flags/sveltekit';

export const showSummerSale = flag<boolean>({
  key: 'summer-sale',
  async decide() { return false; },
  origin: 'https://example.com/flags/summer-sale/',
  description: 'Show Summer Holiday Sale Banner, 20% off',
  options: [
    { value: false, label: 'Hide' },
    { value: true, label: 'Show' },
  ],
});

Evaluation Context

Use identify to segment users. Headers and cookies are normalized:

import { flag } from 'flags/sveltekit';

interface Entities {
  user?: { id: string };
}

export const exampleFlag = flag<boolean, Entities>({
  key: 'identify-example-flag',
  identify({ headers, cookies }) {
    const userId = cookies.get('user-id')?.value;
    return { user: userId ? { id: userId } : undefined };
  },
  decide({ entities }) {
    return entities?.user?.id === 'user1';
  },
});

Deduplication

Extract identify as a named function and reuse across flags. Calls are deduped by function identity:

import type { ReadonlyHeaders, ReadonlyRequestCookies } from 'flags';
import { flag } from 'flags/sveltekit';

interface Entities {
  visitorId?: string;
}

function identify({
  cookies,
  headers,
}: {
  cookies: ReadonlyRequestCookies;
  headers: ReadonlyHeaders;
}): Entities {
  const visitorId =
    cookies.get('visitorId')?.value ?? headers.get('x-visitorId');
  return { visitorId };
}

export const flag1 = flag<boolean, Entities>({
  key: 'flag1',
  identify,
  decide({ entities }) { /* ... */ },
});

export const flag2 = flag<boolean, Entities>({
  key: 'flag2',
  identify,
  decide({ entities }) { /* ... */ },
});

Precompute

Why both Routing Middleware and reroute

  • middleware.ts handles full page visits in production (runs before CDN)
  • reroute handles client-side navigations and dev-time routing
  • Middleware has access to cookies and private env vars; reroute runs on client and must defer to server

Step 1: Create flag group

// src/lib/flags.ts
import { flag } from 'flags/sveltekit';

export const firstPricingABTest = flag({
  key: 'firstPricingABTest',
  decide: () => false,
});

export const secondPricingABTest = flag({
  key: 'secondPricingABTest',
  decide: () => false,
});
// src/lib/precomputed-flags.ts
import { precompute } from 'flags/sveltekit';
import { firstPricingABTest, secondPricingABTest } from './flags';

export const pricingFlags = [firstPricingABTest, secondPricingABTest];

export async function computeInternalRoute(pathname: string, request: Request) {
  if (pathname === '/pricing') {
    return '/pricing/' + (await precompute(pricingFlags, request));
  }
  return pathname;
}

Step 2: Set up reroute hook

// src/hooks.ts
export async function reroute({ url, fetch }) {
  if (url.pathname === '/pricing') {
    const destination = new URL('/api/reroute', url);
    destination.searchParams.set('pathname', url.pathname);
    return fetch(destination).then((response) => response.text());
  }
}
// src/routes/api/reroute/+server.ts
import { text } from '@sveltejs/kit';
import { computeInternalRoute } from '$lib/precomputed-flags';

export async function GET({ url, request }) {
  const destination = await computeInternalRoute(
    url.searchParams.get('pathname')!,
    request,
  );
  return text(destination);
}

Step 3: Set up middleware

// middleware.ts
import { rewrite } from '@vercel/edge';
import { normalizeUrl } from '@sveltejs/kit';
import { computeInternalRoute } from './src/lib/precomputed-flags';

export const config = { matcher: ['/pricing'] };

export default async function middleware(request: Request) {
  const { url, denormalize } = normalizeUrl(request.url);
  if (url.pathname === '/pricing') {
    return rewrite(
      denormalize(await computeInternalRoute(url.pathname, request)),
    );
  }
}

Step 4: Read precomputed values

// src/routes/pricing/[code]/+page.server.ts
import type { PageServerLoad } from './$types';
import { firstPricingABTest, secondPricingABTest } from '$lib/flags';
import { pricingFlags } from '$lib/precomputed-flags';

export const load: PageServerLoad = async ({ params }) => {
  const flag1 = await firstPricingABTest(params.code, pricingFlags);
  const flag2 = await secondPricingABTest(params.code, pricingFlags);
  return {
    first: `First: ${flag1}`,
    second: `Second: ${flag2}`,
  };
};
<!-- src/routes/pricing/[code]/+page.svelte -->
<script>
  let { data } = $props();
</script>

<p>{data.first}</p>
<p>{data.second}</p>

Enable ISR

// src/routes/pricing/[code]/+page.server.ts
export const config = {
  isr: { expiration: false },
};

Enable prerendering

import { generatePermutations } from 'flags/sveltekit';
import { pricingFlags } from '$lib/precomputed-flags';

export const prerender = true;

export async function entries() {
  return (await generatePermutations(pricingFlags)).map((code) => ({ code }));
}

Dashboard Pages

// src/lib/flags.ts
import { flag } from 'flags/sveltekit';

export const showNewDashboard = flag<boolean>({
  key: 'showNewDashboard',
  decide({ cookies }) {
    return cookies.get('showNewDashboard')?.value === 'true';
  },
});
// src/routes/+page.server.ts
import type { PageServerLoad } from './$types';
import { showNewDashboard } from '$lib/flags';

export const load: PageServerLoad = async () => {
  const dashboard = await showNewDashboard();
  return { title: dashboard ? 'New Dashboard' : 'Old Dashboard' };
};

Marketing Pages

Combine precompute with visitor ID generation for A/B tests on static pages:

Middleware with visitor ID

// middleware.ts
import { rewrite } from '@vercel/edge';
import { parse } from 'cookie';
import { normalizeUrl } from '@sveltejs/kit';
import { computeInternalRoute, createVisitorId } from './src/lib/precomputed-flags';

export const config = { matcher: ['/examples/marketing-pages'] };

export default async function middleware(request: Request) {
  const { url, denormalize } = normalizeUrl(request.url);

  let visitorId = parse(request.headers.get('cookie') ?? '').visitorId || '';
  if (!visitorId) {
    visitorId = createVisitorId();
    request.headers.set('x-visitorId', visitorId);
  }

  return rewrite(
    denormalize(await computeInternalRoute(url.pathname, request)),
  );
}

Flags with identify

// src/lib/flags.ts
import { flag } from 'flags/sveltekit';

interface Entities {
  visitorId?: string;
}

function identify({ cookies, headers }) {
  const visitorId =
    cookies.get('visitorId')?.value ?? headers.get('x-visitorId');
  if (!visitorId) throw new Error('Visitor ID not found');
  return { visitorId };
}

export const firstMarketingABTest = flag<boolean, Entities>({
  key: 'firstMarketingABTest',
  identify,
  decide({ entities }) {
    if (!entities?.visitorId) return false;
    return /^[a-n0-5]/i.test(entities.visitorId);
  },
});

The x-visitorId header ensures the visitor ID is available even on the first request before the cookie is set.

Source: SKILL.md on GitHub

No third-party reports yet.

Signed by skilld at ce31fdf. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 9 hours ago.

Activeupdated 13 hours ago
Other metadata
summary
Flags SDK guidance — declare flags with flag(), connect provider adapters, manage Vercel Flags via the vercel flags CLI, precompute static variants, and set up the Flags Explorer.
metadata
{
  "priority": 6,
  "docs": [
    "https://vercel.com/docs/flags",
    "https://flags-sdk.dev"
  ],
  "sitemap": "https://vercel.com/sitemap.xml",
  "pathPatterns": [
    "flags.ts",
    "flags.tsx",
    "lib/flags.ts",
    "src/flags.ts",
    "lib/flags/**",
    "src/flags/**",
    ".well-known/vercel/flags/**"
  ],
  "importPatterns": [
    "flags/next",
    "flags/sveltekit",
    "flags/react",
    "flags",
    "@flags-sdk/*",
    "@vercel/flags"
  ],
  "bashPatterns": [
    "\\bvercel\\s+flags\\b",
    "\\bvc\\s+flags\\b",
    "\\bnpm\\s+(install|i|add)\\s+[^\\n]*\\bflags\\b",
    "\\bpnpm\\s+(install|i|add)\\s+[^\\n]*\\bflags\\b",
    "\\bbun\\s+(install|i|add)\\s+[^\\n]*\\bflags\\b",
    "\\byarn\\s+add\\s+[^\\n]*\\bflags\\b",
    "\\bnpm\\s+(install|i|add)\\s+[^\\n]*@flags-sdk/",
    "\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@flags-sdk/",
    "\\bbun\\s+(install|i|add)\\s+[^\\n]*@flags-sdk/",
    "\\byarn\\s+add\\s+[^\\n]*@flags-sdk/"
  ],
  "promptSignals": {
    "phrases": [
      "feature flag",
      "feature flags",
      "flags sdk",
      "vercel flags",
      "flags explorer",
      "feature gate",
      "feature gating",
      "a/b test",
      "a/b testing",
      "ab test",
      "ab testing",
      "flag rollout",
      "gradual rollout",
      "percentage rollout",
      "kill switch",
      "flag variant",
      "flag adapter",
      "precompute flags",
      "traffic split",
      "targeting rules",
      "flag rules",
      "flag segments",
      "flag targeting",
      "flag evaluations",
      "flag overrides",
      "existing flag"
    ],
    "allOf": [
      [
        "flag",
        "rollout"
      ],
      [
        "flag",
        "variant"
      ],
      [
        "flag",
        "toggle"
      ],
      [
        "flag",
        "experiment"
      ],
      [
        "experiment",
        "variant"
      ],
      [
        "split",
        "test"
      ]
    ],
    "anyOf": [
      "flag",
      "experiment",
      "experimentation",
      "rollout",
      "variant"
    ],
    "noneOf": [
      "command-line flag",
      "command line flag",
      "cli flag",
      "compiler flag",
      "flag emoji",
      "rolling release"
    ],
    "minScore": 6
  }
}
retrieval
{
  "aliases": [
    "feature flags",
    "feature toggles",
    "vercel flags",
    "flags sdk",
    "a/b testing"
  ],
  "intents": [
    "add a feature flag",
    "run an a/b test",
    "gate a feature",
    "roll out gradually",
    "manage flags from cli"
  ],
  "entities": [
    "Flags SDK",
    "Vercel Flags",
    "Flags Explorer",
    "vercelAdapter",
    "precompute",
    "FLAGS_SECRET"
  ]
}
chainTo
[
  {
    "pattern": "precompute\\s*\\(|generatePermutations|flags/next.*precompute",
    "targetSkill": "routing-middleware",
    "message": "Precompute pattern detected — loading Routing Middleware guidance for the middleware rewrites that serve static flag variants."
  },
  {
    "pattern": "@flags-sdk/(edge|global)-config|create(Edge|Global)ConfigAdapter",
    "targetSkill": "vercel-storage",
    "message": "Global Config flag adapter detected — loading Vercel storage guidance for Global Config setup and limits."
  }
]

README badge

README badge for vercel-labs/vercel-plugin/flags-sdk