Feature Flags
Section titled “Feature Flags”Bridge Feature Flags evaluates locally — the module keeps the flag rules in memory, evaluates against in-process context, and receives rule changes live. A flag check is a synchronous O(1) lookup: no network call, no await, safe in hot request paths.
Flags work standalone: BridgeFlagsModule is auth-free and does not require any other Bridge module.
import { Module } from '@nestjs/common';import { BridgeFlagsModule } from '@nebulr-group/bridge-nestjs/flags';
@Module({ imports: [ BridgeFlagsModule.forRoot({ apiBaseUrl: 'https://api.thebridge.dev', apiKey: process.env.BRIDGE_API_KEY!, }), ],})export class AppModule {}forRootAsync is available when the API key comes from ConfigModule. The module is @Global() — register once, inject anywhere.
Programmatic checks — BridgeFlagsService
Section titled “Programmatic checks — BridgeFlagsService”import { Injectable } from '@nestjs/common';import { BridgeFlagsService } from '@nebulr-group/bridge-nestjs/flags';
@Injectable()export class ReportsService { constructor(private readonly flags: BridgeFlagsService) {}
generate(userId: string) { // Synchronous — local evaluation, no network call if (this.flags.flag('use_new_pipeline', false, { identity: userId })) { return this.generateV2(); } return this.generateV1(); }}flag<T>(key, defaultValue, context?) returns the evaluated value, typed from your default (boolean | string | number | JSON object). The default is mandatory — it’s what you get when the flag isn’t configured or Bridge is unreachable. A flag call can never break your app.
Identity on the backend
Section titled “Identity on the backend”A server process is not “a user,” so the SDK never invents an identity — you pass one per eval (or per request scope):
// On behalf of the requesting userthis.flags.flag('feature_x', false, { identity: req.user.id });
// Sticky per-tenant behavior (webhooks, queues)this.flags.flag('new_pipeline', false, { identity: tenantId });
// System-level flag, no identitythis.flags.flag('worker_v2_enabled', false);Percentage rollouts require identity. If a rollout rule is active and no identity is passed, the SDK refuses the rollout and returns the safe default with a warning — it will not silently randomize per call.
Route guards — @RequireFlag
Section titled “Route guards — @RequireFlag”Gate handlers on a flag with the guard + decorator pair:
import { Controller, Get, UseGuards } from '@nestjs/common';import { RequireFlag, BridgeFlagGuard } from '@nebulr-group/bridge-nestjs/flags';
@Controller('exports')@UseGuards(BridgeFlagGuard)export class ExportsController { @Get() @RequireFlag('exports_enabled') list() { /* … */ }
// Non-boolean flags: gate on a specific value @Get('beta') @RequireFlag('export_mode', 'v1', { equals: 'v2' }) betaExport() { /* … */ }}RequireFlag(key, defaultValue = false, options?) — options.equals gates non-boolean flags on a specific value; options.optional: true skips the guard instead of rejecting (useful for kill switches that should disable a route without 403’ing).
Flag values as handler parameters — @Flag
Section titled “Flag values as handler parameters — @Flag”import { Controller, Get } from '@nestjs/common';import { Flag } from '@nebulr-group/bridge-nestjs/flags';
@Controller('home')export class HomeController { @Get() home(@Flag({ key: 'show_new_home', defaultValue: false }) showNew: boolean) { return showNew ? this.newHome() : this.classicHome(); }}For typed reusable params, wrap with useFlagParam<T>(key, defaultValue).
Frontend context propagation — BridgeContextInterceptor
Section titled “Frontend context propagation — BridgeContextInterceptor”When your frontend also evaluates flags for the same user, both sides must agree on identity or buckets split-brain. The frontend Bridge SDKs send the eval context in the x-bridge-context header; BridgeContextInterceptor reads it and binds it to the request, so guard checks, @Flag params, and request-scoped evals automatically use the same identity the frontend used:
import { APP_INTERCEPTOR } from '@nestjs/core';import { BridgeContextInterceptor } from '@nebulr-group/bridge-nestjs/flags';
@Module({ providers: [{ provide: APP_INTERCEPTOR, useClass: BridgeContextInterceptor }],})export class AppModule {}Missing or malformed headers are a no-op — the request falls back to the module’s global context.
Security rule: the propagated context should carry identity and custom attributes only. Never trust client-sent role/plan attributes — read those from server-side sources (see attribute providers below).
Bridge-managed attributes
Section titled “Bridge-managed attributes”To target rules on verified auth state (bridge:user.role, bridge:tenant.plan), register an AuthAttributeProvider that reads your verified JWT claims:
import { AuthAttributeProvider } from '@nebulr-group/bridge-auth-core';
// once at bootstrapthis.flags.bridge.registerAttributeProvider( new AuthAttributeProvider({ getClaims: () => getCurrentClaims() }),);Providers are consulted synchronously on every eval; dev-supplied attributes win on key collision (the admin UI surfaces collisions).
Pull mode
Section titled “Pull mode”Runtimes that can’t hold a live connection can use the pull cache (BridgePullCache, default TTL 30 s) — inject via @Inject(BRIDGE_PULL_CACHE). Evals stay local; the rule set refreshes by polling instead of push.
Multi-type values
Section titled “Multi-type values”const maxUploads = this.flags.flag('max_uploads', 10); // numberconst mode = this.flags.flag('pipeline_mode', 'stable'); // stringconst rateLimit = this.flags.flag('rate_limit', { window: 60, max: 100 }); // JSONA type mismatch (admin stored a different type than your default suggests) returns the default and logs a warning.
On-demand checks over the Bridge API
Section titled “On-demand checks over the Bridge API”@RequireFlag / BridgeFlagsService (above) evaluate locally and receive live updates. If you’d rather not run a flags client — for simple route gating or an occasional check — @RequireFeatureFlag / FeatureFlagService evaluate flags on demand over the Bridge API instead, keyed on the user’s access token, with a 5-minute per-token cache:
@RequireFeatureFlag({ all: ['premium-tier', 'reports-v2'] }) // decoratorawait this.featureFlags.isEnabled('pdf-export', accessToken); // service, async, 5-min cache@RequireFeatureFlag (with any/all requirement objects) and FeatureFlagService.isEnabled / evaluateRequirement / bulkEvaluate work with boolean flags.
Which to use: reach for @RequireFlag / BridgeFlagsService.flag() when you want synchronous, multi-type, live-updating evaluation; reach for @RequireFeatureFlag / FeatureFlagService when you just need a quick boolean check and don’t want to run a flags client.