A lightweight, self-hostable feature flag service.
Define flags, target them to users or environments, and evaluate them from any application via REST API or TypeScript SDK.
βββββββββββββββ ββββββββββββββββ ββββββββββββ
β Dashboard ββββββΆβ Go API ββββββΆβ Postgres β
β React/Vite β β chi router β ββββββββββββ
βββββββββββββββ β β ββββββββββββ
β ββββββΆβ Redis β
βββββββββββββββ β β ββββββββββββ
β SDK (npm) ββββββΆβ :8080 β
β TypeScript β ββββββββββββββββ
βββββββββββββββ
- API β Go, chi router, pgx, go-redis. Handles flag CRUD, evaluation, and audit logging.
- Dashboard β React, Vite, TailwindCSS, TanStack Query, dnd-kit. Manages flags and views audit logs.
- SDK β TypeScript, zero dependencies. Wraps the evaluation endpoint with in-memory caching.
- Go 1.23+
- Node.js 22+ and pnpm
- Docker (for Postgres and Redis)
git clone https://github.com/davigiroux/flagkit.git
cd flagkit
pnpm installdocker compose up -dcp api/.env.example api/.env
cd api && go run ./cmd/serverOn first run, an API key is printed to the console:
========================================
BOOTSTRAP API KEY (save this!):
fk_abc123...
========================================
pnpm --filter dashboard devOpen http://localhost:5173, go to Settings, and paste your API key.
All endpoints (except /health) require Authorization: Bearer <api_key>.
| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check (no auth) |
GET |
/flags |
List all flags |
POST |
/flags |
Create a flag |
GET |
/flags/:key |
Get a flag by key |
PATCH |
/flags/:key |
Update a flag |
DELETE |
/flags/:key |
Delete a flag |
POST |
/flags/:key/toggle |
Toggle enabled state |
GET |
/evaluate/:key |
Evaluate a flag |
GET |
/audit |
List audit logs (paginated) |
curl -X POST http://localhost:8080/flags \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"key": "new-checkout",
"name": "New Checkout",
"environment": "production",
"rules": [
{ "type": "allowlist", "userIds": ["vip-1", "vip-2"] },
{ "type": "percentage", "value": 50 }
]
}'curl "http://localhost:8080/evaluate/new-checkout?user_id=user-123&environment=production" \
-H "Authorization: Bearer $API_KEY"Response:
{
"enabled": true,
"reason": "rollout",
"flagKey": "new-checkout",
"evaluatedAt": "2026-03-14T10:00:00Z"
}Rules evaluate top-to-bottom. First match wins.
- If
enabledisfalseβ{ enabled: false, reason: "flag_disabled" } - If rule is
allowlistanduserIdis in the list β{ enabled: true, reason: "allowlist" } - If rule is
percentageandhash(flagKey + userId) % 100 < valueβ{ enabled: true, reason: "rollout" } - No rule matched β
{ enabled: false, reason: "no_match" }
Percentage rollout uses consistent hashing (FNV-32a) β the same user always gets the same result.
npm install flagkit-sdkimport { FlagKit } from 'flagkit-sdk'
const flags = new FlagKit({
apiKey: 'fk_...',
baseUrl: 'https://your-api.railway.app',
ttl: 30_000, // cache TTL in ms (default: 30s)
})
const enabled = await flags.isEnabled('new-checkout', {
userId: 'user-123',
environment: 'production',
})
if (enabled) {
// show new checkout
}- Results are cached in memory with a configurable TTL
- On cache miss, the SDK calls the API and stores the result
- On API failure, it returns the last cached value (stale fallback)
- If no cached value exists and the API is down, it returns
false(safe default)
| Field | Type | Description |
|---|---|---|
id |
UUID | Primary key |
key |
string | Unique slug identifier |
name |
string | Display name |
description |
string | Optional description |
enabled |
boolean | Global on/off toggle |
environment |
enum | production, staging, or development |
rules |
JSON | Array of targeting rules |
createdAt |
timestamp | Creation time |
updatedAt |
timestamp | Last update time |
Percentage rollout β rolls out to a percentage of users based on consistent hashing:
{ "type": "percentage", "value": 50 }User allowlist β enables for specific user IDs:
{ "type": "allowlist", "userIds": ["user-1", "user-2"] }| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgres://flagkit:flagkit@localhost:5432/flagkit?sslmode=disable |
Postgres connection string |
REDIS_URL |
redis://localhost:6379 |
Redis connection string |
PORT |
8080 |
Server port |
CORS_ORIGINS |
http://localhost:5173 |
Comma-separated allowed origins |
| Variable | Default | Description |
|---|---|---|
VITE_API_URL |
http://localhost:8080 |
API base URL |
# Run everything
docker compose up -d # Postgres + Redis
cd api && go run ./cmd/server # API on :8080
pnpm --filter dashboard dev # Dashboard on :5173
# Tests
cd api && go test ./... # Go tests (hash, evaluation, audit diff)
pnpm --filter flagkit-sdk test # SDK tests
# Build
pnpm --filter dashboard build # Dashboard production build
pnpm --filter flagkit-sdk build # SDK ESM + CJS buildThe project includes Dockerfiles for both the API and Dashboard:
- API:
api/Dockerfileβ multi-stage Go build, runs migrations on startup - Dashboard:
dashboard/Dockerfileβ Vite build served via nginx
Railway services needed:
- API β from
api/Dockerfile, env vars:DATABASE_URL,REDIS_URL,PORT,CORS_ORIGINS - Dashboard β from
dashboard/Dockerfile, build arg:VITE_API_URL - Postgres β Railway managed
- Redis β Railway managed
MIT