| name | setup-feature-flag |
|---|---|
| description | Sets up a new client-side feature flag in github-ui. Registers the flag, adds usage in code, updates tests, and validates. Use when adding a new feature that needs to be gated behind a feature flag. |
Use this skill when adding a new feature flag to gate UI changes in github-ui.
- You are working in the
github-uirepository. - You know the purpose of the feature being flagged.
- ALWAYS use
snake_casefor flag names. - ALWAYS insert flags in alphabetical order in the registry array — the list is
@eslint-sort-arrayenforced. - ALWAYS use
isFeatureEnabledfrom@github-ui/feature-flags— never the deprecateduseFeatureFlaghook from@github-ui/react-core/use-feature-flag. - Flag names are visible to users in the
<script id="client-env">attribute. Avoid revealing secret project names. - Feature flags only gate UI — they do not enforce access control. Always enforce access checks at the data/API layer separately.
Pick a descriptive snake_case name. Follow existing conventions:
- Prefix with the feature area (e.g.,
copilot_,issues_,prs_,dashboard_) - Be specific enough to distinguish from other flags
- Keep it non-obvious if the feature is confidential
Confirm the flag name with the user before proceeding.
Add the flag name to the jsFeatureFlags array in:
packages/feature-flags/client-feature-flags.ts
Insert it in alphabetical order within the array. The array uses as const and is enforced by @eslint-sort-array.
If the flag is CSS-only (toggling styles without JS logic), add it to the cssFeatureFlags array instead.
If the flag should be evaluated server-side but not sent to the client, add it to serverOnlyFeatureFlags.
In the component or module that needs the gate, import and check the flag:
import {isFeatureEnabled} from '@github-ui/feature-flags'
// Check the flag
if (isFeatureEnabled('your_flag_name')) {
// New feature code
}For React components, call it at the top of the component body:
import {isFeatureEnabled} from '@github-ui/feature-flags'
export function MyComponent() {
const myFeatureEnabled = isFeatureEnabled('your_flag_name')
if (!myFeatureEnabled) {
return <OldBehavior />
}
return <NewBehavior />
}For CSS-only flags, use the data attribute selector:
[data-css-features~="your_flag_name" i] {
/* styles behind flag */
}Mock the feature flag in tests. Two approaches:
Option A — Mock the module (preferred for unit tests):
import {isFeatureEnabled} from '@github-ui/feature-flags'
vi.mock('@github-ui/feature-flags', () => ({
isFeatureEnabled: vi.fn(),
}))
// In a test:
vi.mocked(isFeatureEnabled).mockReturnValue(true)Option B — Mock the client env (for integration-style tests):
import {mockClientEnv} from '@github-ui/client-env'
mockClientEnv({featureFlags: ['your_flag_name']})Write tests for both states (flag on and flag off) to ensure the old behavior is preserved when the flag is disabled.
Run the following checks for each modified package:
# Lint the feature-flags package (checks alphabetical order)
npm run lint -w @github-ui/feature-flags -- --fix client-feature-flags.ts
# Lint, typecheck, and test the package you changed
npm run lint -w @github-ui/<your-package> -- --fix
npm run tsc -w @github-ui/<your-package>
npm test -w @github-ui/<your-package>To toggle the flag locally against a running dev server, use:
script/toggle-feature-flag enable your_flag_nameOr override via URL query parameter (staff only):
?_features=your_flag_name # enable
?_features=!your_flag_name # disable
Or via cookie:
document.cookie = "_features=your_flag_name; path=/"After completing these steps, the PR should include:
- The flag registered in
packages/feature-flags/client-feature-flags.ts - Feature-gated code using
isFeatureEnabled('flag_name') - Tests covering both flag-on and flag-off states
- All lint, typecheck, and test checks passing
- Feature flags should be short-lived. Plan to clean up the flag once the feature is fully rolled out.
- The server-side flag (in the Rails monolith) must also be created separately via DevPortal or Vexi for the flag to evaluate as enabled in production.
- Check the flag with a safe default:
FeatureFlag.vexi.enabled?("your_flag", actor, default: false)on the server side.