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.
A middleware is an object with one or more of these methods:
beforeEvaluation
: Executed before a flag is evaluatedafterEvaluation
: Executed after a flag is evaluatedonError
: Executed when an error occurs during evaluationHere'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 methods are executed in the following order:
beforeEvaluation
: All registered middleware in the order they were addedafterEvaluation
: All registered middleware in reverse order (last to first)onError
: All registered middleware if an error occursThis pattern allows earlier middleware to "wrap" later middleware.
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);
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);
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);
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;
}
};
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;
}
};
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;
}
};
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;
}
};
const errorReportingMiddleware = {
name: 'ErrorReportingMiddleware',
onError: (error, flagKey) => {
// Send to error monitoring service
Sentry.captureException(error, {
tags: {
flagKey,
source: 'flagdeck'
},
level: 'warning'
});
}
};
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)afterEvaluation
handler gets the result as modified by the previous handlerFor 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'
}
}));
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);
Always Return Results: In afterEvaluation
, always return a result (original or modified)
Keep Middleware Focused: Each middleware should have a single responsibility
Make Middleware Configurable: Use factory functions to make middleware reusable
Handle Async Properly: Use async/await or return Promises for asynchronous operations
Error Handling: Implement error handling in middleware to avoid breaking the evaluation chain
Avoid Side Effects: Middleware should not have unintended side effects
Performance: Keep middleware efficient, especially for high-traffic applications
Start using Flagdeck today
Simple feature flag management for modern development teams.
Product
© 2025 Flagdeck. All rights reserved.