Flagdeck
Pricing
Docs
Get Started

Docs

Docs


SDKs

  • JavaScript
    • Getting Started
    • Value Types
    • Evaluation Context
    • Configuration
    • Real Time Updates
    • Error Handling
    • Middleware
    • Offline Mode
    • API Reference
  • React

API Reference

  • Authentication
  • Flags
  • Environments
  • Projects

Middleware

Middleware provides a powerful way to extend and customize the behavior of the Flagdeck SDK. You can use middleware to add logging, modify evaluation results, implement custom targeting rules, and more.

Middleware Basics

A middleware is an object with one or more of these methods:

  • beforeEvaluation: Executed before a flag is evaluated
  • afterEvaluation: Executed after a flag is evaluated
  • onError: Executed when an error occurs during evaluation

Here's a simple example of middleware that logs flag evaluations:

const loggingMiddleware = {
  name: 'LoggingMiddleware',
  beforeEvaluation: (flagKey, context) => {
    console.log(`Evaluating flag: ${flagKey}`, context);
  },
  afterEvaluation: (flagKey, result, context) => {
    console.log(`Flag ${flagKey} evaluated to:`, result.value);
    return result; // Important: return the result
  },
  onError: (error, flagKey) => {
    console.error(`Error evaluating flag ${flagKey}:`, error);
  }
};

// Register the middleware
flagdeck.use(loggingMiddleware);

Middleware Execution Order

Middleware methods are executed in the following order:

  1. beforeEvaluation: All registered middleware in the order they were added
  2. Actual flag evaluation occurs
  3. afterEvaluation: All registered middleware in reverse order (last to first)
  4. onError: All registered middleware if an error occurs

This pattern allows earlier middleware to "wrap" later middleware.

Modifying Evaluation Results

One of the most powerful middleware capabilities is modifying evaluation results:

const overrideMiddleware = {
  name: 'OverrideMiddleware',
  afterEvaluation: (flagKey, result, context) => {
    // Override specific flags for testing
    if (flagKey === 'new-feature' && context?.attributes?.isQaTester) {
      return {
        ...result,
        value: true,
        reason: 'Overridden for QA tester'
      };
    }

    // Important: return the original result for all other cases
    return result;
  }
};

flagdeck.use(overrideMiddleware);

Complete Middleware Example

Here's a more comprehensive middleware example with all methods:

const analyticsMiddleware = {
  name: 'AnalyticsMiddleware',

  beforeEvaluation: (flagKey, context) => {
    // Record evaluation start time
    const startTime = Date.now();

    // Store in a "global" variable for this evaluation cycle
    this.evaluationData = {
      startTime,
      flagKey,
      userId: context?.userId || 'anonymous'
    };

    // You can perform async operations if needed
    // Just return a Promise that resolves when ready
    return Promise.resolve();
  },

  afterEvaluation: (flagKey, result, context) => {
    // Calculate evaluation duration
    const duration = Date.now() - this.evaluationData.startTime;

    // Track the evaluation in analytics
    analytics.track('Feature Flag Evaluated', {
      flagKey,
      value: result.value,
      source: result.source,
      userId: context?.userId,
      duration,
      timestamp: Date.now()
    });

    // Return the unchanged result
    return result;
  },

  onError: (error, flagKey) => {
    // Track errors
    analytics.track('Feature Flag Error', {
      flagKey,
      errorMessage: error.message,
      errorCode: error.code,
      userId: this.evaluationData?.userId,
      timestamp: Date.now()
    });
  }
};

flagdeck.use(analyticsMiddleware);

Asynchronous Middleware

Middleware methods can be asynchronous by returning promises:

const asyncMiddleware = {
  name: 'AsyncMiddleware',

  beforeEvaluation: async (flagKey, context) => {
    // Perform async operations
    await someAsyncOperation();

    // You can also modify the context
    const enrichedContext = await enrichContext(context);
    return enrichedContext; // Optional: return modified context
  },

  afterEvaluation: async (flagKey, result, context) => {
    // Perform async processing
    await recordResult(flagKey, result.value);

    // You can also modify the result
    const enhancedResult = await enhanceResult(result);
    return enhancedResult; // Return possibly modified result
  }
};

flagdeck.use(asyncMiddleware);

Common Middleware Use Cases

1. Logging and Debugging

const debugMiddleware = {
  name: 'DebugMiddleware',
  beforeEvaluation: (flagKey, context) => {
    console.group(`Flag Evaluation: ${flagKey}`);
    console.log('Context:', context);
    console.time('Evaluation Time');
  },
  afterEvaluation: (flagKey, result, context) => {
    console.log('Result:', result);
    console.timeEnd('Evaluation Time');
    console.groupEnd();
    return result;
  }
};

2. Analytics Tracking

const analyticsMiddleware = {
  name: 'AnalyticsMiddleware',
  afterEvaluation: (flagKey, result, context) => {
    // Send to analytics platform
    mixpanel.track('Feature Flag', {
      flagKey,
      value: result.value,
      userId: context?.userId
    });
    return result;
  }
};

3. Context Enrichment

const contextEnrichmentMiddleware = {
  name: 'ContextEnrichmentMiddleware',
  beforeEvaluation: (flagKey, context) => {
    // Always include current time
    const enrichedContext = {
      ...context,
      attributes: {
        ...context?.attributes,
        timestamp: Date.now(),
        timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
        locale: navigator.language
      }
    };
    return enrichedContext;
  }
};

4. Testing Overrides

const testOverrideMiddleware = {
  name: 'TestOverrideMiddleware',
  beforeEvaluation: (flagKey, context) => {
    // Check if there are URL query parameters for flag overrides
    const urlParams = new URLSearchParams(window.location.search);
    const override = urlParams.get(`flag_${flagKey}`);

    if (override) {
      // Store the override to use in afterEvaluation
      this.override = {
        flagKey,
        value: override === 'true'
      };
    }
  },
  afterEvaluation: (flagKey, result, context) => {
    // Apply override if it exists for this flag
    if (this.override && this.override.flagKey === flagKey) {
      const overrideResult = {
        ...result,
        value: this.override.value,
        source: 'override',
        reason: 'URL parameter override'
      };
      return overrideResult;
    }
    return result;
  }
};

5. Error Reporting

const errorReportingMiddleware = {
  name: 'ErrorReportingMiddleware',
  onError: (error, flagKey) => {
    // Send to error monitoring service
    Sentry.captureException(error, {
      tags: {
        flagKey,
        source: 'flagdeck'
      },
      level: 'warning'
    });
  }
};

Middleware Chaining

You can register multiple middleware instances, and they will be executed in sequence:

// Register multiple middleware
flagdeck.use(loggingMiddleware);
flagdeck.use(analyticsMiddleware);
flagdeck.use(overrideMiddleware);

Remember:

  • beforeEvaluation middleware runs in the registration order (first to last)
  • afterEvaluation middleware runs in reverse order (last to first)
  • Each afterEvaluation handler gets the result as modified by the previous handler

Creating Reusable Middleware

For more complex middleware, create a factory function:

function createAnalyticsMiddleware(analyticsProvider, options = {}) {
  return {
    name: 'AnalyticsMiddleware',
    afterEvaluation: (flagKey, result, context) => {
      const eventName = options.eventName || 'Feature Flag Evaluated';

      analyticsProvider.track(eventName, {
        flagKey,
        value: result.value,
        userId: context?.userId,
        ...options.additionalProperties
      });

      return result;
    },
    onError: (error, flagKey) => {
      if (options.trackErrors) {
        analyticsProvider.track('Feature Flag Error', {
          flagKey,
          error: error.message
        });
      }
    }
  };
}

// Usage
flagdeck.use(createAnalyticsMiddleware(mixpanel, {
  trackErrors: true,
  additionalProperties: {
    appVersion: '1.2.3'
  }
}));

TypeScript Support

For TypeScript users, the middleware interface is fully typed:

import { Flagdeck, EvaluationMiddleware, EvaluationContext, EvaluationResult } from '@flagdeck/js';

const typedMiddleware: EvaluationMiddleware = {
  name: 'TypedMiddleware',

  beforeEvaluation: (
    flagKey: string,
    context?: Partial<EvaluationContext>
  ): void | Partial<EvaluationContext> | Promise<void | Partial<EvaluationContext>> => {
    console.log(`Evaluating ${flagKey}`);
  },

  afterEvaluation: (
    flagKey: string,
    result: EvaluationResult,
    context?: Partial<EvaluationContext>
  ): EvaluationResult | Promise<EvaluationResult> => {
    console.log(`Flag ${flagKey} = ${result.value}`);
    return result;
  },

  onError: (
    error: Error,
    flagKey?: string
  ): void | Promise<void> => {
    console.error(`Error in ${flagKey}:`, error);
  }
};

flagdeck.use(typedMiddleware);

Best Practices

  1. Always Return Results: In afterEvaluation, always return a result (original or modified)

  2. Keep Middleware Focused: Each middleware should have a single responsibility

  3. Make Middleware Configurable: Use factory functions to make middleware reusable

  4. Handle Async Properly: Use async/await or return Promises for asynchronous operations

  5. Error Handling: Implement error handling in middleware to avoid breaking the evaluation chain

  6. Avoid Side Effects: Middleware should not have unintended side effects

  7. Performance: Keep middleware efficient, especially for high-traffic applications

Start using Flagdeck today

Simple feature flag management for modern development teams.

Get Started
Flagdeck

Modern feature flag management platform to help you deploy with confidence.

Product

FeaturesPricingDocumentation

© 2025 Flagdeck. All rights reserved.