Comprehensive rules for designing, implementing, and maintaining feature toggles/flags in TypeScript-based web services and SPAs.
Stop deploying incomplete features to production. Stop rolling back entire releases because one feature broke. Stop explaining to stakeholders why that "small change" took down checkout for 20 minutes.
You know the drill: feature branches pile up, merge conflicts multiply, and when you finally deploy, you're crossing your fingers that nothing breaks. Meanwhile, your team is blocked waiting for code reviews on massive PRs that touch half the codebase.
Here's what's actually happening:
Feature toggles solve this by decoupling deployment from release. Deploy your code continuously, control feature visibility dynamically.
These Cursor Rules transform how you build, deploy, and manage features in TypeScript applications. Instead of managing feature toggles as an afterthought, you'll implement them as first-class citizens with proper typing, testing, and lifecycle management.
What you get:
Deploy incomplete features behind toggles. Your main branch stays deployable, merge conflicts disappear, and your team moves faster.
// Before: Massive feature branch sits for weeks
// After: Deploy immediately, control visibility
const isNewCheckoutEnabled = isFlagEnabled('checkout-enable-paypal');
return isNewCheckoutEnabled ? <NewCheckout /> : <LegacyCheckout />;
When something breaks, flip a toggle instead of rolling back deployments.
// Circuit breaker pattern - 5 consecutive failures = auto-disable
if (!isFlagEnabled('search-use-elastic')) {
return legacySearch(query); // Fallback ready
}
return elasticSearch(query); // Happy path last
Every feature automatically tests both enabled and disabled states.
// Table-driven tests for comprehensive coverage
describe.each([
['enabled', true],
['disabled', false]
])('PayPal checkout %s', (state, flagValue) => {
beforeEach(() => mockFlag('checkout-enable-paypal', flagValue));
// Test both paths automatically
});
Morning standup: "I'll deploy the search rewrite today, but keep it toggled off until testing completes."
Afternoon deployment: Feature goes live behind a toggle, QA tests in production with toggle enabled for their accounts only.
End of day: Toggle enabled for 10% of users, monitoring dashboards confirm everything's working.
Alert: "Search response time spiked to 2.3 seconds"
Response: One Slack command: /toggle search-use-elastic off
Result: Instant fallback to legacy search, incident resolved in 30 seconds instead of 20 minutes.
Launch experiments without separate infrastructure:
const isNewOnboardingEnabled = isFlagEnabled('onboarding-streamlined-flow');
// Automatically segments users, tracks conversion rates
return isNewOnboardingEnabled ? <StreamlinedOnboarding /> : <StandardOnboarding />;
npm install @openfeature/server-sdk @openfeature/web-sdk
npm install -D @types/node
// src/feature-toggles/index.ts
export type FeatureFlag =
| { key: 'checkout-enable-paypal'; enabled: boolean }
| { key: 'search-use-elastic'; enabled: boolean }
| { key: 'onboarding-streamlined-flow'; enabled: boolean };
export const isFlagEnabled = <T extends FeatureFlag['key']>(key: T): boolean => {
return flagClient.getBoolean(key, false); // Fail closed
};
# feature-toggles.yaml
flags:
- key: checkout-enable-paypal
owner: payments-team
createdAt: 2024-01-15
removeBy: 2024-06-15
description: "Enable PayPal payment option in checkout"
export function useFeatureFlag(key: FeatureFlag['key']): boolean {
const [enabled, setEnabled] = useState(false);
useEffect(() => {
const updateFlag = (value: boolean) => setEnabled(value);
flagClient.on(`change:${key}`, updateFlag);
return () => flagClient.off(`change:${key}`, updateFlag);
}, [key]);
return enabled;
}
Set up git hooks to prevent merge without proper flag metadata:
#!/bin/sh
# .git/hooks/pre-commit
node scripts/validate-flags.js || exit 1
Your codebase is already complex enough. Don't let feature management add to that complexity—let it solve it.
These Cursor Rules give you production-tested patterns used by teams shipping millions of requests daily. Every rule enforces best practices that prevent the common pitfalls that turn feature toggles into technical debt.
The question isn't whether you need feature toggles. The question is whether you'll implement them properly from day one, or spend months debugging the homegrown solution you built in a weekend.
Your next deployment can be fearless. Your next feature can launch to 1% of users in production. Your next incident can be resolved with a single toggle flip.
Ready to ship without fear?
You are an expert in TypeScript, Node.js, React, feature-flag SaaS platforms (LaunchDarkly, Split.io, Optimizely, Unleash), and the OpenFeature SDK.
Key Principles
- Ship code continuously; hide incomplete or experimental capabilities behind server-evaluated feature toggles.
- Every toggle is an asset with a lifecycle: create → test → rollout → delete.
- Toggle names MUST be descriptive, kebab-case, prefixed by domain (e.g., checkout-enable-paypal).
- Keep toggles independent; one flag controls exactly one concern.
- Default to OFF. An undefined flag must never enable a feature.
- Prefer server-side evaluation to reduce latency and prevent client tampering.
- All code paths (on/off) MUST be covered by automated tests.
- Remove stale toggles within two iterations to curb technical debt.
TypeScript
- Represent flags with a discriminated union:
```ts
export type FeatureFlag =
| { key: 'checkout-enable-paypal'; enabled: boolean }
| { key: 'search-use-elastic'; enabled: boolean };
```
- Provide a typed access helper:
```ts
export const isFlagEnabled = <T extends FeatureFlag['key']>(key: T): boolean => {
return flagClient.getBoolean(key, false); // 2nd arg = safe default
};
```
- Wrap flag lookup in a pure function; never call SDKs directly in UI components or business logic.
- Use explicit boolean variables: const isCheckoutPaypalEnabled = isFlagEnabled('checkout-enable-paypal');
- Keep toggle files under src/feature-toggles/; one <flag-name>.spec.ts per toggle.
- Enforce lint rule eslint-plugin-unicorn/no-nested-ternary to keep conditional paths readable when toggles nest.
Error Handling & Validation
- Fail closed: if the flag service is unreachable, treat every flag as disabled.
- Log at WARN level on evaluation failure with context (flag key, user id, env).
- Guard critical sections:
```ts
if (!isFlagEnabled('search-use-elastic')) {
return legacySearch(query);
}
// happy path last
return elasticSearch(query);
```
- Use circuit-breaker pattern: when 5 consecutive evaluations fail, short-circuit to default OFF for 10 min.
- Validate incoming flag payloads against zod schema before caching.
React (Framework-Specific Rules)
- Provide a generic hook:
```ts
export function useFeatureFlag(key: FeatureFlag['key']): boolean {
const [enabled, setEnabled] = useState(false);
useEffect(() => flagClient.on('change:'+key, setEnabled));
return enabled;
}
```
- Never hard-code UI text in toggle conditions; wrap whole components:
```tsx
return isNewNavbar ? <NewNavbar/> : <OldNavbar/>;
```
- Use Suspense + lazy() for large gated features to avoid bundle bloat when OFF.
Node.js (API layer)
- Evaluate flags once per request and attach to req.flags to avoid redundant SDK calls.
- Expose a /__flags endpoint (GET, internal only) returning current values for observability.
- Disable cache headers on flag endpoints; rely on SDK streaming updates instead.
Testing
- Unit: Table-driven tests exercising true/false states.
- Integration: In CI run matrix [FLAG=on, FLAG=off].
- E2E: Use environment-scoped flags (e.g., env=ci) so tests can flip without affecting prod.
- Snapshot UI for both states using Playwright.
Performance
- Cache evaluations in an L1 in-process store (TTL ≤ 60 s) to reduce p99 latency.
- Batch SDK calls: use variationDetailAll() where available.
- Avoid client-side polling; use streaming/BroadcastChannel to propagate updates.
Security
- NEVER expose sensitive rollout rules to front-end; send only boolean result.
- Authenticate SDK keys with TLS 1.2+; rotate every 90 days.
- Use RBAC: only release-engineer and product-owner roles can toggle prod flags.
Lifecycle & Governance
- Each flag requires owner, creation date, target removal date in YAML registry:
```yaml
- key: checkout-enable-paypal
owner: payments-team
removeBy: 2024-12-31
```
- Git hook prevents merging new flags lacking owner/removeBy.
- Jira automation opens cleanup ticket when removeBy − 14 days.
Tooling & Integration
- Adopt OpenFeature as abstraction; plug in LaunchDarkly for prod, InMemoryProvider for tests.
- CLI workflow:
```bash
$ ofctl flag create checkout-enable-paypal --owner payments-team --default off
$ ofctl rollout --percentage 10 --env staging
```
- Metrics: Push on/off counts to Prometheus: feature_flag_enabled{flag="checkout-enable-paypal"} 123
Directory Structure
- src/
├── feature-toggles/
│ ├── index.ts # helper exports
│ ├── checkout-enable-paypal.ts
│ └── checkout-enable-paypal.spec.ts
├── services/
├── ui/
└── __flags__ # generated at build for static sites
Common Pitfalls
- Forgetting to delete flags → bloated conditionals → readability loss.
- Client-side evaluation exposing business strategy.
- Coupling multiple behaviours to one toggle.
Checklist before Merge
- [ ] Flag name follows kebab-case & domain prefix.
- [ ] Owner & removeBy recorded.
- [ ] Both ON/OFF unit tests added.
- [ ] Documentation updated in /docs/feature-toggles.md.