Feature flags should not cost $400 a month. Gatedly is an open-source feature flag service built in Go — LaunchDarkly-level control with gradual rollouts, percentage targeting, and custom rule conditions, without the enterprise pricing.
Why Another Feature Flag Tool
LaunchDarkly is the gold standard for feature flags. It is also priced for enterprises. Flagsmith, Unleash, and the other open-source alternatives are either complex to self-host or missing features that matter in production — specifically percentage-based rollouts, allow/deny lists on the same flag, and custom condition rules evaluated server-side.
Gatedly is designed for small teams and solo developers who want to ship confidently with gradual rollouts but do not need a $400/month SaaS. It is self-hostable in a single Docker container and has a React dashboard for managing flags without touching config files.
Architecture
The backend is Go — chosen for its low memory footprint (important for self-hosting on cheap VPS instances), fast startup, and excellent standard library for HTTP APIs. PostgreSQL stores flag definitions, rules, and evaluation logs. The evaluation engine runs entirely in-process; there is no external dependency required at evaluation time.
Gatedly backend (Go)
└── Flag CRUD API (Gin)
└── Evaluation engine (in-process)
└── SDK endpoint: GET /evaluate/:flagKey
└── PostgreSQL (flags, rules, environments, logs)
└── Server-Sent Events (real-time flag updates)
React dashboard
└── Flag management UI
└── Targeting rules editor
└── Evaluation log viewerThe Evaluation Engine
Flag evaluation is the core piece. A flag is evaluated for a context — typically a user ID with optional attributes. The engine walks through the flag's rules in priority order:
type EvaluationContext struct {
UserID string
Attributes map[string]any
}
// Rule priority order:
// 1. Allow list (user IDs get flag regardless of anything else)
// 2. Deny list (user IDs never get flag)
// 3. Custom conditions (attribute matching)
// 4. Percentage rollout (consistent hash of userID + flagKey)
// 5. Default variationPercentage rollouts use a consistent hash of the user ID combined with the flag key. This means the same user always gets the same bucket, rollouts are sticky, and changing the percentage only affects users at the boundary — not the entire user base.
func percentageBucket(userID, flagKey string) int {
h := fnv.New32a()
h.Write([]byte(userID + ":" + flagKey))
return int(h.Sum32() % 100)
}
func (e *Engine) evaluatePercentage(ctx EvaluationContext, flag Flag) bool {
bucket := percentageBucket(ctx.UserID, flag.Key)
return bucket < flag.RolloutPercentage
}Custom Condition Rules
Beyond percentage and lists, flags support condition rules evaluated against context attributes. Rules support string equality, contains, starts-with, numeric comparisons, and semantic version comparisons — the last one being important for mobile app rollouts where you want to target users on a specific app version range.
// Example: enable for premium users on app version >= 2.1.0
{
"conditions": [
{ "attribute": "plan", "operator": "eq", "value": "premium" },
{ "attribute": "appVersion", "operator": "semver_gte", "value": "2.1.0" }
],
"match": "all"
}Real-Time Flag Updates
When a flag is updated in the dashboard, connected SDK clients receive the change via Server-Sent Events without polling. The Go SSE implementation maintains a registry of active subscribers per environment. Flag updates are broadcast to all subscribers in that environment within milliseconds.
The SDK
A lightweight TypeScript SDK wraps the evaluation API. It caches flag values locally and subscribes to the SSE stream for updates. The cache means flag evaluations are synchronous after the initial fetch — zero latency at the point of evaluation.
import { Gatedly } from '@gatedly/sdk';
const client = new Gatedly({ apiKey: process.env.GATEDLY_KEY });
await client.init();
// Synchronous after init — no async/await needed
const showNewCheckout = client.isEnabled('new-checkout', {
userId: user.id,
attributes: { plan: user.plan, country: user.country }
});Status
Gatedly is in active development. The evaluation engine, flag CRUD API, percentage rollouts, allow/deny lists, and custom conditions are complete. The SSE real-time updates and dashboard UI are in progress. The TypeScript SDK is planned for after the core API stabilises.