Skip to content

thomasblaymire/switchflag

Repository files navigation

Switchflag

Feature flag platform built for the edge. Ship features safely with targeting rules, percentage rollouts, and per-environment configuration.

Features · Getting Started · SDK · Tech Decisions · Testing · Observability

CI E2E Coverage npm Commits Stars


Switchflag Dashboard

Live Website: switchflag.com


Switchflag gives you a full-stack feature flagging system: a dashboard to manage flags, an SDK to evaluate them, and observability to see what's happening. Flags are fetched once and evaluated client-side, so there's no network round-trip on every getFlag() call.


Why Build This?

Most feature flag services are either expensive SaaS with per-seat pricing, or self-hosted tools with heavy infrastructure requirements. Switchflag is designed to run cheaply on edge infrastructure (Turso for the database, Vercel for compute) while still supporting the features teams actually need: targeting rules, gradual rollouts, multi-environment configs, and audit trails.


Features

  • Targeting rules — 10 operators (equals, contains, startsWith, in, percent, etc.) evaluated in order, first match wins
  • Percentage rollouts — Consistent hashing (Java's String.hashCode() algorithm) ensures the same user always lands in the same bucket
  • Multi-environment — Each flag has independent configuration per environment (dev, staging, production)
  • Multi-tenant — Organization-based access control with owner/admin/member roles and email invitations
  • Billing & plans — Stripe-powered subscription tiers (Developer, Startup, Business) with enforced limits on flags, requests, and team members
  • Audit logging — Every flag change is recorded with who changed what and when
  • Evaluation analytics — Daily aggregated counts per flag per environment, no massive event tables
  • Full observability — Server-side OpenTelemetry traces + metrics, browser-side Grafana Faro RUM (Web Vitals, JS errors, page views)
  • Accessibility — WCAG 2.1 AA compliance enforced via automated axe-core checks on every page
  • Type-safe SDK — Zero-dependency, published on npm with standard, edge, Next.js, and React entry points

Feature Flags

Projects

Activity Log


Architecture

┌─────────────────────────────────────────────────────────────┐
│                        Monorepo                             │
│                                                             │
│  apps/                                                      │
│    dashboard/     Admin UI (Next.js 15, React 19)           │
│    docs/          Documentation site (Fumadocs)             │
│    web/           Marketing site                            │
│    sandbox/       SDK integration demo                      │
│                                                             │
│  packages/                                                  │
│    sdk/           Client SDK (standard + edge variants)     │
│    evaluator/     Rule engine + consistent hashing          │
│    shared/        Zod schemas, types, test factories        │
│    db/            Turso/Drizzle schema + repositories       │
│    ui/            Component library (shadcn/ui)             │
│                                                             │
│  tooling/                                                   │
│    eslint-config, typescript-config, tailwind-config        │
└─────────────────────────────────────────────────────────────┘

The SDK ships in two variants:

  • Standard — In-memory Map cache, suited for long-lived server processes
  • Edge — Uses fetch with cache: 'force-cache' and next: { revalidate: 60 }, delegating caching to the edge network since edge functions are ephemeral

Tech Decisions

Area Choice Why
Database Turso (libSQL) + Drizzle ORM SQLite at the edge with embedded replicas. Drizzle gives type-safe queries with zero runtime overhead
Auth Better Auth Handles session management, email verification, password reset, and org/member models without a separate auth service
Monorepo Turborepo + pnpm Topological task ordering (^build dependencies), cached builds, workspace protocol for internal packages
Validation Zod + @t3-oss/env-nextjs Zod schemas as single source of truth for runtime validators and TypeScript types. Environment variables validated at build time
Testing Vitest + MSW + Playwright + axe-core Vitest workspace across all packages. MSW for API mocking. Playwright for E2E with visual regression and WCAG 2.1 AA accessibility checks
Styling Tailwind CSS 4 + shadcn/ui Shared Tailwind preset across apps. shadcn gives copy-paste components without a runtime dependency
Observability OpenTelemetry + Grafana Faro OTel for server traces/metrics (every server action is wrapped in withSpan()), Faro for browser RUM. Both disabled when env vars are unset — zero overhead in local dev
Email Resend Transactional emails for invitations, verification, and password reset. Opt-in via RESEND_API_KEY
Payments Stripe Subscription billing with webhook-driven plan enforcement. Three tiers with limits on flags, requests, and team members
Security Rate limiting + security headers Upstash Redis rate limiting on auth and API routes. Security headers (CSP, HSTS, X-Frame-Options) applied via middleware. API keys hashed before storage
Code quality Husky + commitlint + Prettier + syncpack Conventional commits enforced on every commit. Syncpack ensures consistent dependency versions across the monorepo
Releases Changesets + GitHub Actions Automated versioning and npm publishing for @switchflag/sdk. Bundle verification ensures no Zod leaks and evaluator is properly inlined

Getting Started

Prerequisites

  • Node.js >= 20
  • pnpm 9.15.2
  • A Turso database (free tier works)

Setup

git clone https://github.com/thomasblaymire/switchflag.git
cd switchflag
pnpm install
cp .env.example apps/dashboard/.env.local

Fill in your .env.local:

Variable Required What it does
TURSO_DATABASE_URL Yes Your Turso database URL
TURSO_AUTH_TOKEN Yes Turso auth token
BETTER_AUTH_SECRET Yes Encryption key for sessions (min 32 chars)
BETTER_AUTH_URL Yes http://localhost:3001 for local dev
RESEND_API_KEY No Enables transactional emails (invites, verification, password reset)
STRIPE_SECRET_KEY No Enables billing and subscription management
STRIPE_WEBHOOK_SECRET No Verifies incoming Stripe webhook events
UPSTASH_REDIS_REST_URL No Enables rate limiting on auth and API routes
UPSTASH_REDIS_REST_TOKEN No Auth token for Upstash Redis
OTEL_EXPORTER_OTLP_ENDPOINT No OTLP collector — enables server-side tracing and metrics
OTEL_EXPORTER_OTLP_HEADERS No Auth headers for your OTLP collector
NEXT_PUBLIC_GRAFANA_FARO_URL No Grafana Faro collector — enables browser RUM

All optional integrations (email, billing, rate limiting, observability) gracefully degrade when their env vars are unset — local development requires only the four required variables.

pnpm dev          # Dashboard on localhost:3001

SDK Usage

import { createClient } from '@switchflag/sdk'

const client = createClient({ apiKey: 'sf_live_...' })

const result = await client.getFlag('new-checkout-flow', {
  userId: 'user-123',
  attributes: {
    plan: 'pro',
    country: 'GB',
  },
})

if (result.enabled) {
  // new checkout
}

For edge runtimes (Vercel Edge Functions, Cloudflare Workers):

import { createEdgeClient } from '@switchflag/sdk/edge'

const client = createEdgeClient({ apiKey: 'sf_live_...' })
// Same API, but caching is delegated to the edge network

How Evaluation Works

  1. SDK fetches the flag configuration once from the API
  2. Targeting rules are evaluated locally, in order — first match wins
  3. For percentage rollouts, the evaluator hashes userId:ruleId to produce a deterministic 0-99 bucket
  4. The same user always gets the same result for the same rule (no flicker between variants)

Security

  • API key hashing — Keys are hashed before storage; only a prefix is retained for display
  • Rate limiting — Upstash Redis-backed limits on SDK API (100 req/min), sign-in (10 req/min), and sign-up (5 req/min)
  • Security headers — CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, and Permissions-Policy applied via Next.js middleware
  • Environment validation — All env vars validated at build time via @t3-oss/env-nextjs with Zod schemas

Observability

Server-side (OpenTelemetry)

Every server action is wrapped in a traced span with custom attributes:

project.create  { project.slug: "my-app" }
flag.toggle     { flag.key: "new-checkout", environment: "production" }
flag.delete     { flag.key: "old-feature" }

A custom flag.change.count metric tracks mutation frequency. The OTel SDK is initialized via Next.js instrumentation.ts hook, dynamically imported to avoid bundling Node.js code into edge functions.

Client-side (Grafana Faro)

Faro captures Web Vitals (LCP, INP, CLS, TTFB, FCP), JS errors, console errors, page views, and fetch timing. Browser traces propagate traceparent headers to server traces, giving you full request visibility from click to database.

Both layers are opt-in — unset the env vars and they produce zero overhead.


Testing

pnpm test                # Unit tests across all packages
pnpm test:watch          # Watch mode

# E2E
cd apps/dashboard
pnpm test:e2e            # Headless Playwright
pnpm test:e2e:ui         # Interactive UI mode

Unit & integration tests

  • Vitest workspace runs tests across all packages in parallel
  • MSW (Mock Service Worker) for network-level API mocking without coupling tests to implementation details
  • Testing Library for component tests driven by user behavior, not implementation
  • Factory builders (createFlag(), createFlagEnvironment(), createTargetingRule(), etc.) so tests construct realistic data without boilerplate
  • Distribution tests verify hash uniformity across 1000 simulated users in the evaluator package

E2E tests (Playwright)

  • Multi-persona testing — Pre-seeded test accounts (owner, member, admin) with distinct roles for role-specific assertions
  • Functional tests — Auth flows, project CRUD, API key management, billing, team invitations, onboarding per role
  • Accessibility tests — axe-core scans every page against WCAG 2.1 AA, violations fail the build
  • Visual regression — Screenshot comparison against Linux baselines, auto-generated in CI
  • Smoke tests — Critical path subset for fast feedback

Ephemeral preview environments

Every PR gets an isolated Turso database with seeded test accounts. A GitHub Action creates the database, applies migrations, seeds users, and posts a PR comment with login credentials. When the PR is closed, the database is destroyed.

CI pipelines

Workflow Trigger What it does
CI Push / PR Lint, typecheck, unit tests, build, coverage upload to Codecov
E2E Push / PR Playwright tests (functional, a11y, screenshots), auto-commits baselines
Preview DB PR open / close Creates isolated Turso database per PR, seeds test accounts, posts credentials comment
Release Push to main Changesets versioning, SDK bundle verification (no Zod leaks, evaluator inlined), npm publish
Bundle size PR Analyzes JS bundle sizes for dashboard, web, and SDK, reports top 10 chunks

Scripts

pnpm dev           # Start dev servers
pnpm build         # Build all packages (topologically ordered)
pnpm test          # Run tests
pnpm lint          # Lint all packages
pnpm typecheck     # TypeScript checks
pnpm format        # Prettier formatting

Project Structure

switchflag/
├── apps/
│   ├── dashboard/              # Admin UI
│   │   ├── src/
│   │   │   ├── app/            # Next.js App Router (pages, layouts, error boundaries)
│   │   │   ├── actions/        # Server actions (traced with OTel spans)
│   │   │   ├── components/     # React components
│   │   │   └── lib/            # Auth, DB, telemetry, email, rate limiting
│   │   ├── e2e/                # Playwright tests (functional, a11y, screenshots)
│   │   └── grafana/            # Importable Grafana dashboard JSON
│   ├── docs/                   # Documentation site (Fumadocs + MDX)
│   ├── web/                    # Marketing homepage
│   └── sandbox/                # SDK demo app
├── packages/
│   ├── sdk/                    # Standard + edge client SDKs (published on npm)
│   ├── evaluator/              # Targeting rules + percentage rollouts
│   ├── shared/                 # Zod schemas, types, test factories
│   ├── db/                     # Turso/Drizzle schema, repositories
│   └── ui/                     # shadcn/ui component library
└── tooling/
    ├── eslint-config/
    ├── typescript-config/
    └── tailwind-config/

Built with Next.js 15, React 19, Turso, Drizzle, and TypeScript

About

Full-stack feature flag platform with edge-evaluated SDK, multi-tenant RBAC, distributed tracing (OpenTelemetry + Grafana Faro), and a custom rules engine with consistent hashing for percentage rollouts.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages