Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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