Open-source feature flag and progressive delivery platform built with TypeScript.
Current status: Experimental (v0.1.0). PulseFlag is a portfolio-grade systems and platform engineering project. It is not production-ready.
PulseFlag manages feature flags across development, staging, and production. PostgreSQL is the source of truth, Redis accelerates evaluation and distributes invalidations, a Fastify API exposes management and SDK contracts, and a Next.js dashboard provides the control plane. The independent evaluation engine handles targeting and deterministic percentage rollout without importing HTTP, database, cache, or UI code.
A useful feature flag system is more than a CRUD screen. It must make the same decision for the same subject, isolate environments, protect SDK credentials, bound untrusted evaluation payloads, propagate changes, degrade safely when cache infrastructure is unavailable, and explain why a variation was served. PulseFlag keeps those concerns explicit and inspectable.
- Projects with automatically provisioned development, staging, and production environments
- Boolean, string, and number flags with typed variations
- Independent per-environment flag configuration
- Priority-ordered targeting rules with AND conditions
- Stable 10,000-bucket percentage rollouts using integer basis points
- Remote single and bulk evaluation APIs
- Environment-scoped SDK keys stored only as prefix and SHA-256 hash
- PostgreSQL source of truth with Drizzle schema and SQL migration
- Local and Redis evaluation snapshot cache with bounded TTL
- Redis Pub/Sub invalidation and Server-Sent Events
- Audit log with cursor pagination and secret-free metadata
- JavaScript/TypeScript SDK with timeout, cancellation, and typed fallbacks
- Next.js and Tailwind management dashboard
- Structured Pino logging with credential redaction
- Health, readiness, CORS, body limits, rate limiting, OpenAPI, and graceful shutdown
- Multi-stage non-root API and web containers
flowchart LR
Dashboard["Dashboard / Next.js"] --> API["PulseFlag API / Fastify"]
SDK["JavaScript SDK"] --> API
API --> PostgreSQL["PostgreSQL / source of truth"]
API --> Redis["Redis"]
API --> Core["Evaluation Core"]
Redis --> Cache["Snapshot cache"]
Redis --> PubSub["Pub/Sub invalidation"]
PubSub --> SSE["SSE clients"]
The deployment is a modular monolith rather than a collection of microservices. Package boundaries keep the decision engine reusable while one API process owns lifecycle and infrastructure resources. See Architecture.
The pure @pulseflag/evaluation-core package applies this fixed sequence:
flowchart TD
Request["SDK evaluation"] --> Key["Authenticate environment SDK key"]
Key --> Snapshot["Load environment flag snapshot"]
Snapshot --> Enabled{"Flag enabled?"}
Enabled -->|No| Disabled["Disabled variation"]
Enabled -->|Yes| Rules["Rules by ascending priority"]
Rules -->|First match| Target["Target variation"]
Rules -->|No match| Rollout["Deterministic rollout bucket"]
Rollout -->|Allocated| Variation["Rollout variation"]
Rollout -->|Not configured| Default["Default variation"]
Results include FLAG_DISABLED, TARGET_MATCH, ROLLOUT, DEFAULT, or FLAG_NOT_FOUND. The engine never uses Math.random().
Conditions support equals, not_equals, in, not_in, contains, starts_with, and ends_with. Every condition inside one rule must match. Rules are evaluated by ascending numeric priority and the first matching rule wins. Context key is mandatory; optional attributes may be string, finite number, boolean, or a bounded string array.
PulseFlag hashes flagKey:environmentKey:context.key with stable 32-bit FNV-1a and maps it to a bucket in 0..9999. Flag, environment, and context identity therefore produce the same bucket across requests and API instances.
Allocations use integer basis points: 100 = 1%, 2500 = 25%, and 10000 = 100%. When configured, allocations must total exactly 10,000. Cumulative intervals select a variation without floating-point rounding.
FeatureFlag owns identity and variations. EnvironmentFlagConfig owns enabled state, default and disabled variations, targeting rules, and rollout for one environment. A flag can therefore be open in development, partially rolled out in staging, and conservative in production.
The web application includes overview, projects, project detail, environments, feature flags, a complete environment-aware flag editor, audit log, and API key management. The administrator enters PULSEFLAG_ADMIN_TOKEN; the dashboard keeps it in session storage and sends it only as the API bearer credential.
import { PulseFlagClient } from "@pulseflag/sdk-js";
const client = new PulseFlagClient({
baseUrl: "https://flags.example.com",
sdkKey: process.env.PULSEFLAG_SDK_KEY!,
timeoutMs: 1500,
});
const enabled = await client.getBoolean("new-checkout", false, {
key: "user-123",
attributes: { country: "BR", plan: "premium" },
});getBoolean, getString, and getNumber return their caller-provided fallback for expected transport, timeout, response, or type failures. evaluate and bulkEvaluate expose structured results and errors for callers that need them. close() cancels outstanding and future work.
Management APIs live under /api/v1 and require Authorization: Bearer <admin-token>. Evaluation and stream endpoints accept x-pulseflag-sdk-key or a bearer SDK key. The development API exposes OpenAPI UI at /docs. See API Reference.
Prerequisites are Node.js 22+, pnpm 10+, PostgreSQL, and optionally Redis.
cp .env.example .env
pnpm install
pnpm db:migrate
pnpm devReplace PULSEFLAG_ADMIN_TOKEN before exposing the service. The dashboard defaults to http://localhost:3000 and API to http://localhost:8080.
| Variable | Default/example | Purpose |
|---|---|---|
NODE_ENV |
development |
Runtime policy and docs visibility |
PULSEFLAG_API_HOST |
0.0.0.0 |
API bind address |
PULSEFLAG_API_PORT |
8080 |
API port |
DATABASE_URL |
PostgreSQL URL | Required source-of-truth connection |
REDIS_URL |
Redis URL | Optional cache, Pub/Sub, and degraded fallback |
PULSEFLAG_ADMIN_TOKEN |
change-me |
Administrator bearer token; invalid in production |
PULSEFLAG_ALLOWED_ORIGINS |
http://localhost:3000 |
Comma-separated CORS allowlist |
PULSEFLAG_CACHE_TTL_SECONDS |
30 |
Local and Redis snapshot TTL |
PULSEFLAG_EVALUATION_RATE_LIMIT |
600 |
Requests per minute per source IP |
PULSEFLAG_MAX_BULK_FLAGS |
100 |
Maximum flags per bulk evaluation |
NEXT_PUBLIC_PULSEFLAG_API_URL |
http://localhost:8080 |
Browser-visible dashboard API URL |
LOG_LEVEL |
info |
Pino log level |
The relational model includes projects, environments, flags, variations, environment configs, rules, conditions, rollout allocations, API keys, and audit events. Foreign keys, unique constraints, checks, and lookup indexes are present in both the Drizzle schema and db/migrations/0000_initial.sql.
Redis is not authoritative. Evaluation snapshots are cached with a TTL. Writes commit to PostgreSQL, delete local and Redis entries, publish a compact invalidation event, and notify connected SSE clients. API instances evict matching local snapshots when they receive Pub/Sub events. If Redis cannot connect or an operation fails, evaluation loads from PostgreSQL.
GET /api/v1/stream authenticates an environment SDK key and streams flag.created, flag.updated, and flag.deleted events. Events identify project, environment, and flag but contain no values, rules, user context, or credentials.
Dockerfile.api and Dockerfile.web are multi-stage, run as non-root users, and target Node.js 24 Alpine. compose.yml defines PostgreSQL, Redis, API, web, health checks, and persistent volumes.
PULSEFLAG_ADMIN_TOKEN="replace-with-a-long-secret" docker compose up --buildContainer files are provided for deployment review. They were intentionally not executed for v0.1.
apps/
api/ Fastify management and evaluation API
web/ Next.js management dashboard
packages/
evaluation-core/ pure deterministic decision engine
sdk-js/ typed network SDK
shared/ domain contracts and Zod schemas
db/migrations/ PostgreSQL migration SQL
docs/ architecture, evaluation, API, security, ADRs
Dockerfile.api
Dockerfile.web
compose.yml
Administrative routes require a constant-time compared bearer token. SDK credentials use 256 bits of randomness, reveal the complete secret once, and store only a prefix and SHA-256 digest. Logs redact authorization fields. Production rejects the example administrator token. CORS is an allowlist, request bodies are bounded, evaluation is rate-limited, inputs are validated, and internal stack details are not returned. See Security.
- ADR 0001: Use TypeScript
- ADR 0002: Use Fastify
- ADR 0003: PostgreSQL as source of truth
- ADR 0004: Redis cache and Pub/Sub
- ADR 0005: Deterministic rollouts
- ADR 0006: Independent evaluation core
- V0.1 uses one global administrator token; there are no users, organizations, or RBAC.
- SDK key hashes are unkeyed SHA-256 because secrets are high entropy; deployment-level database access remains sensitive.
- Evaluation is remote; the SDK does not maintain an offline flag cache or consume SSE yet.
- Redis fallback preserves correctness but increases PostgreSQL load and loses cross-instance realtime invalidation while degraded.
- Audit events identify administrator actions but not individual actors.
- No automated tests, CI, runtime validation, benchmark, or load result is claimed for this version.
- Dashboard access should be restricted at the network edge until user authentication exists.
- automated test suite
- GitHub Actions CI
- user accounts, organizations, RBAC, and team management
- richer targeting operators
- improved SDK caching
- OpenTelemetry
- Go, Python, and Java SDKs
- flag prerequisites
- scheduled flag changes
- approval workflows
- experimentation and analytics
- automated progressive rollout
- multi-region experiments
No milestone has a promised date.
Focused issues and pull requests are welcome. Keep the evaluation core infrastructure-independent, preserve strict TypeScript, avoid secrets in fixtures or logs, and document changes to evaluation semantics.
PulseFlag is available under the MIT License. Copyright 2026 Thiago Montozo.