Best practices
Guidelines for implementing contexts effectively and avoiding common pitfalls.
Context design
Do
Create attributes that support targeting and rollout use cases
Only add attributes you’ll actually use for targeting or analytics:
// Good - actionable attributes
const context = {
kind: "user",
key: "user-123",
plan: "enterprise", // For entitlement targeting
region: "us-west", // For regional rollouts
betaTester: true // For beta feature access
};
// Bad - unused attributes
const context = {
kind: "user",
key: "user-123",
favoriteColor: "blue", // Not used for targeting
shoeSize: 10 // Not used for targeting
};
Create multiple identifiers for different contexts and consistency boundaries
Define separate contexts for user, session, device, etc.:
const multiContext = {
kind: "multi",
user: {
key: "user-123",
plan: "enterprise"
},
session: {
key: "session-abc",
anonymous: true
},
device: {
key: "device-789",
platform: "iOS"
}
};
Define private attributes when targeting on sensitive information
Mark any PII or sensitive data as private:
const context = {
kind: "user",
key: "user-123",
email: "jane@example.com",
ipAddress: "192.168.1.1",
_meta: {
privateAttributes: ["email", "ipAddress"]
}
};
Do not
Use values derived from PII or sensitive values as keys
Never use email, phone numbers, or other PII directly as keys:
// Bad - PII as key
const context = { kind: "user", key: "jane@example.com" };
// Good - opaque key, PII as private attribute
const context = {
kind: "user",
key: "user-123",
email: "jane@example.com",
_meta: { privateAttributes: ["email"] }
};
Rapidly change attributes in client-side SDKs
Avoid timestamp or frequently changing attributes:
// Bad - changes every render
const context = {
kind: "user",
key: "user-123",
currentTime: Date.now()
};
// Good - stable attributes
const context = {
kind: "user",
key: "user-123",
sessionStartTime: sessionStart
};
Mix value types or sources for an attribute within the same project
Keep attribute types consistent:
// Bad - inconsistent types across applications
// iOS: { accountType: "premium" }
// Web: { accountType: 1 }
// Good - consistent types
// All apps: { accountType: "premium" }
Flag evaluation
Do
Call variation/variationDetail where the flag will be used
Evaluate flags at the point of use:
// Good - evaluate where needed
function renderButton() {
const showNewButton = ldClient.variation("new-button-ui", context, false);
return showNewButton ? <NewButton /> : <OldButton />;
}
// Bad - evaluate unnecessarily
function loadPage() {
const allFlags = ldClient.allFlags(); // Evaluates all flags
// Only use one flag
return allFlags['new-button-ui'];
}
Maintain fallback values that allow the application to function
Choose safe fallback values:
// Good - safe fallbacks
const maxRetries = ldClient.variation("max-retries", context, 3);
const featureEnabled = ldClient.variation("new-feature", context, false);
// Bad - no fallback or unsafe fallback
const maxRetries = ldClient.variation("max-retries", context); // undefined
const criticalFeature = ldClient.variation("payment-enabled", context, true); // unsafe default
Write code with cleanup in mind
Minimize flag usage to simplify cleanup:
// Good - single evaluation point
function PaymentForm() {
const useNewPaymentFlow = ldClient.variation("new-payment-flow", context, false);
return useNewPaymentFlow ? <NewPaymentForm /> : <OldPaymentForm />;
}
// Bad - multiple evaluation points
function PaymentForm() {
if (ldClient.variation("new-payment-flow", context, false)) {
// New flow code
}
const buttonText = ldClient.variation("new-payment-flow", context, false)
? "Pay Now"
: "Submit Payment";
// More evaluations...
}
Do not
Use allFlags/allFlagsState for use cases other than passing values to another application
Only use allFlags when absolutely necessary:
// Bad - unnecessary allFlags call
const flags = ldClient.allFlags();
if (flags['feature-x']) {
// Use feature
}
// Good - targeted evaluation
if (ldClient.variation("feature-x", context, false)) {
// Use feature
}
// Good - passing to another application
const flagState = ldClient.allFlagsState(context);
bootstrapFrontend(flagState);
Call variation/variationDetail without using the flag value
Don’t evaluate flags you won’t use:
// Bad - unused evaluation
ldClient.variation("feature-flag", context, false);
// Flag value never used
// Good - use the value
const enabled = ldClient.variation("feature-flag", context, false);
if (enabled) {
enableFeature();
}
Use flags without a plan
Have a clear purpose and cleanup plan:
// Bad - unclear purpose
const flag1 = ldClient.variation("temp-flag", context, false);
const flag2 = ldClient.variation("test-something", context, false);
// Good - clear purpose and naming
const useNewCheckoutFlow = ldClient.variation(
"checkout-flow-v2-rollout", // Clear name
context,
false // Safe fallback to old flow
);
// TODO: Remove this flag after 100% rollout - JIRA-123
Code references
Add ld-find-code-refs to your CI pipeline to track flag usage:
# .github/workflows/launchdarkly-code-refs.yml
name: LaunchDarkly Code References
on: push
jobs:
find-code-refs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: LaunchDarkly Code References
uses: launchdarkly/find-code-references@v2
with:
project-key: my-project
access-token: ${{ secrets.LD_ACCESS_TOKEN }}
Code references enable you to:
- Identify where flags are used in your codebase
- Understand the impact before making flag changes
- Find and remove flags when they’re no longer needed
- Manage technical debt associated with feature flags
Flag planning
Make flag planning part of feature design. Before creating a flag, ask:
- Is this flag temporary or permanent?
- Who is responsible for maintaining the targeting rules in each environment?
- How will this flag be targeted? (By user? By session? By organization?)
- Does this feature have any dependencies? (Other flags? External services?)
- What is the cleanup plan? (When can this flag be removed?)
Minimize flag reach
Use flags in as few places as possible:
// Good - single evaluation point
function App() {
const useNewUI = ldClient.variation("new-ui", context, false);
return <AppLayout newUI={useNewUI} />;
}
// Bad - evaluated throughout the application
function Header() {
if (ldClient.variation("new-ui", context, false)) { /* ... */ }
}
function Sidebar() {
if (ldClient.variation("new-ui", context, false)) { /* ... */ }
}
function Footer() {
if (ldClient.variation("new-ui", context, false)) { /* ... */ }
}
A flag should have a single, well-defined scope. For larger features, consider breaking them into multiple flags using prerequisites.
Failure mode resilience
Connection loss
If the connection to LaunchDarkly is lost:
Client-side SDKs: Context cannot be updated via identify() until connectivity is re-established. Values from the in-memory cache will be served.
Server-side SDKs: Evaluation for any context can still take place using the in-memory feature store.
Initialization failure
If the SDK is unable to initialize:
Both SDK types: Locally cached values will be served if available, otherwise fallback values will be used.
Always provide sensible fallback values that allow your application to function:
// Good - safe degradation
const featureEnabled = ldClient.variation("new-feature", context, false);
// Bad - application breaks if SDK fails
const criticalConfig = ldClient.variation("api-endpoint", context); // undefined