Run it and use the API
A self-hosted feature-flag service. Ship new code switched off, then turn it on from a dashboard: per environment, for chosen groups of users, or for a percentage of them. Apps evaluate flags in memory, with no network call per check, and hear about changes within about a second.
New to feature flags, or not a developer? Start with the plain-language guide: what Switchyard does, and how to use the dashboard step by step.
What it does
- Flags per environment: every project starts with Development and Production, and admins add more (Staging, QA…), each with its own switch, rules and SDK key.
- Targeting: rules on user attributes (
email ends with @acme.com,country is one of IN, SG,age is at least 18,app version is before 2.4.0,signed up after 2026-01-01, regular expressions), checked in order. - Multivariate flags: serve text, numbers or JSON (a button colour, a limit, a config object), not just on/off, with weighted splits like 30% / 70%.
- Segments: define a group once ("Beta testers": listed users plus anyone matching rules) and target it from any flag.
- Prerequisites: a flag only goes past its off value when other flags give the values it needs.
- Percentage rollouts: stable per user, so raising 10% → 50% only ever adds people.
- Kill switch: off overrides every rule, everywhere, within about a second.
- Live updates: Server-Sent Events push each change; polling is the safety net.
- Keeps working through outages: apps keep their last settings when the server is unreachable.
- History with one-click undo: every change, who made it, the settings before and after, and an Undo button.
- Sign in with GitHub: your team uses their own GitHub accounts (from an allowlist), and History names them.
- Roles and protected environments: viewers read, editors change flags and segments, admins do everything; Production starts protected, so only admins change it.
- Approvals: in an environment that requires approval, a change becomes a request that someone else approves before it applies.
- Scheduled changes: switch a flag or change its rules at a set time; the server applies it within about 15 seconds of it.
- Webhooks and Slack: every change posted to a Slack channel as one line, or to your own service as signed JSON.
- API tokens: for scripts and CI, each with a role, optionally limited to one project, optionally expiring.
- Dashboard: an Angular Material admin UI for all of the above, with light and dark themes and a phone layout.
- Clients for Java and JavaScript, which give the same answer for the same user, plus OpenFeature providers for both, so code written against the vendor-neutral OpenFeature API works with Switchyard unchanged.
- Browsers and React: web pages use a public client-side ID; the server evaluates the flags marked for browsers and the page only ever sees their values.
Status: phases 1–9 are built and tested; see PLAN.md. Deploying it publicly on free plans is a step-by-step guide away.
| Folder | What |
|---|---|
server |
The Spring Boot service: admin API and SDK API |
dashboard |
The Angular admin UI |
clients/java |
switchyard-java, the zero-dependency Java client |
clients/js |
switchyard-js, the zero-dependency client for Node, Deno, Bun and edge runtimes, with an OpenFeature provider |
clients/web |
switchyard-web, for web pages, with React hooks |
clients/java-openfeature |
switchyard-openfeature, the OpenFeature provider for Java |
demo |
100 pretend users that light up as you change a flag |
spec |
How flags are evaluated, plus test vectors every client must pass |
benchmarks |
JMH for the Java client, k6 for the SDK API |
docs |
The user guide and the deploy guide |
site |
The public website: a landing page, plus these docs as web pages (npm run build, then npm run preview for http://localhost:4300) |
Run it locally
Needs Java 17+, Docker (for Postgres) and, for the dashboard, Node.js 22.22.3+ or 24.15+.
# 1. Postgres
docker compose up -d
# 2. Server on http://localhost:8080, allowing the dashboard's origin
cd server
SWITCHYARD_ADMIN_TOKEN=dev-admin-token SWITCHYARD_DASHBOARD_ORIGINS=http://localhost:4200 ./mvnw spring-boot:run
# 3. Dashboard on http://localhost:4200 (in another terminal)
cd dashboard
npm install
npm startOpen http://localhost:4200 and sign in with server URL http://localhost:8080 and token dev-admin-token.
No Postgres to hand? With Docker running, ./mvnw spring-boot:test-run starts the server with a throwaway database instead of step 2.
Use it from Java
SwitchyardClient flags = SwitchyardClient.builder(sdkKey).baseUri("http://localhost:8080").build();
if (flags.isEnabled("new-checkout", false)) { ... }
// With targeting rules or a percentage rollout, say who the check is for
EvaluationContext user = EvaluationContext.builder(userId).attribute("email", email).build();
if (flags.isEnabled("new-checkout", user, false)) { ... }
// Multivariate flags: typed values, with a default for anything unexpected
String color = flags.stringVariation("checkout-button", user, "green");How to add the library and how it behaves: clients/java/README.md.
Use it from JavaScript
import { SwitchyardClient } from 'switchyard-js';
const flags = new SwitchyardClient({ sdkKey, baseUrl: 'http://localhost:8080' });
if (flags.isEnabled('new-checkout', { key: userId, attributes: { email } }, false)) { ... }Server-side only (the SDK key can read every rule). Details: clients/js/README.md. For web pages and React, see clients/web. To see both working, run the demo.
Use the API directly
Everything the dashboard does goes through the admin API:
ADMIN="Authorization: Bearer dev-admin-token"
# Create a project. The response shows each environment's SDK key once: keep them.
curl -s -X POST localhost:8080/api/projects -H "$ADMIN" -H "Content-Type: application/json" \
-d '{"key":"shop","name":"Shop"}'
# Create a flag (off everywhere), then turn it on in production
curl -s -X POST localhost:8080/api/projects/shop/flags -H "$ADMIN" -H "Content-Type: application/json" \
-d '{"key":"new-checkout","name":"New checkout"}'
curl -s -X PUT localhost:8080/api/projects/shop/flags/new-checkout/environments/production \
-H "$ADMIN" -H "Content-Type: application/json" -d '{"enabled":true}'
# Staff always, India and Singapore at 50%, everyone else at 10%
curl -s -X PUT localhost:8080/api/projects/shop/flags/new-checkout/environments/production/targeting \
-H "$ADMIN" -H "Content-Type: application/json" -d '{
"rules": [
{ "clauses": [{ "attribute": "email", "operator": "ends_with", "values": ["@acme.com"] }],
"serve": { "value": true } },
{ "clauses": [{ "attribute": "country", "operator": "in", "values": ["IN", "SG"] }],
"serve": { "rollout": { "percentage": 50 } } }
],
"fallthrough": { "rollout": { "percentage": 10 } }
}'
# What a client library sees, with the production SDK key
curl -si localhost:8080/sdk/v1/flags -H "Authorization: Bearer <production sdk key>"
# Repeat with -H 'If-None-Match: "<etag from above>"' to get 304 when nothing changed
# Who changed what, and undo one change by its id
curl -s localhost:8080/api/projects/shop/audit -H "$ADMIN"
curl -s -X POST localhost:8080/api/projects/shop/audit/<id>/revert -H "$ADMIN"Scripts and CI pipelines are better off with an API token (dashboard: API tokens, admins only): it has its own role, can be limited to one project, and History names it.
TOKEN="Authorization: Bearer api-…"
# Switch on in production at 09:00 UTC tomorrow
curl -s -X POST localhost:8080/api/projects/shop/changes -H "$TOKEN" -H "Content-Type: application/json" \
-d '{"flag":"new-checkout","environment":"production","enabled":true,"at":"2026-11-02T09:00:00Z"}'
# What's waiting for an approval or a time
curl -s localhost:8080/api/projects/shop/changes -H "$TOKEN"Scripts using the admin token can name themselves in History with -H "X-Switchyard-Actor: deploy-bot". The admin token also skips "requires approval", for emergencies.
Every endpoint is listed in PLAN.md. Errors use RFC 9457 problem details, with an errors map naming each invalid field. How clients evaluate rules and rollouts is specified in spec/EVALUATION.md.
Configuration (server)
| Variable | Default | Purpose |
|---|---|---|
DATABASE_URL |
jdbc:postgresql://localhost:5432/switchyard |
JDBC URL |
DATABASE_USER / DATABASE_PASSWORD |
switchyard / switchyard |
Database credentials |
DATABASE_POOL_SIZE |
5 |
Connection pool size |
SWITCHYARD_ADMIN_TOKEN |
(empty) | Bearer token for /api/**: scripts, and signing in to the dashboard without GitHub. With neither this nor GitHub sign-in set, the admin API is off |
SWITCHYARD_DASHBOARD_ORIGINS |
(empty) | Comma-separated origins allowed to call the admin API from a browser, e.g. http://localhost:4200. Also the only places GitHub sign-in returns to |
SWITCHYARD_GITHUB_CLIENT_ID / SWITCHYARD_GITHUB_CLIENT_SECRET |
(empty) | A GitHub OAuth app, for GitHub sign-in (how) |
SWITCHYARD_GITHUB_ALLOWED_USERS |
(empty) | GitHub usernames allowed to sign in, comma-separated |
SWITCHYARD_GITHUB_ADMINS |
(empty) | GitHub usernames that are always admins (and may sign in). At least one of these two lists is needed for GitHub sign-in |
SWITCHYARD_GITHUB_DEFAULT_ROLE |
editor |
The role people get on their first sign-in: viewer, editor or admin. Admins change it on the Members page |
SWITCHYARD_GITHUB_CALLBACK_URL |
(from the request) | The callback URL registered with GitHub: <server>/auth/github/callback |
SWITCHYARD_SESSION_TTL |
12h |
How long a GitHub sign-in lasts |
STREAM_MAX_CONNECTIONS |
2000 |
Live-update streams kept open at once; more get 503 and keep polling |
SWITCHYARD_CHANGES_CHECK_EVERY |
PT15S |
How often the server applies scheduled changes that are due (ISO-8601 duration) |
PORT |
8080 |
HTTP port |
Tests
| Part | Command | Notes |
|---|---|---|
| Java client | cd clients/java && ./mvnw verify |
Includes the shared evaluation vectors |
| Server | cd server && ./mvnw verify |
Install the Java client first (./mvnw install in clients/java): the contract tests use it. Integration tests need Docker |
| Dashboard | cd dashboard && npx ng test --watch=false |
Vitest |
| JavaScript client | cd clients/js && npm test |
node --test; includes the shared evaluation vectors |
CI runs all four on every pull request. On main it also builds the server's GraalVM native image and publishes it to GitHub Container Registry.
Stack
Spring Boot 4 · Java 17 · Postgres (Neon in production) · Flyway · JdbcClient · Server-Sent Events · GraalVM native image · Angular 22 + Angular Material · TypeScript · GitHub Actions · Render · Cloudflare Pages