Flagship + AWS Lambda Integration

This project demonstrates how to integrate Flagship feature flags with AWS Lambda, enabling feature flagging and A/B testing in your serverless applications.

📘

Github Repository

https://github.com/flagship-io/flagship-aws-lambda-example

Overview

This guide shows how to:

  • Initialize the Flagship SDK in an AWS Lambda function
  • Configure the SDK for optimal performance in serverless environments
  • Create a visitor object with context data from API Gateway events
  • Fetch feature flags assigned to this visitor
  • Retrieve specific flag values for use in the application
  • Send analytics data back to Flagship
  • Ensure analytics are sent before the Lambda function terminates

Prerequisites

Setup

  1. Clone the example repository:
git clone https://github.com/flagship-io/flagship-aws-lambda-example.git
cd flagship-aws-lambda-example
  1. Install dependencies:
cd hello-world
npm install
  1. Configure your Flagship credentials in the template.yaml file:
Environment:
  Variables:
    FLAGSHIP_ENV_ID: "your-env-id"
    FLAGSHIP_API_KEY: "your-api-key"
  1. Build and deploy your application:
sam build
sam deploy --guided

Initialize the Flagship SDK in an AWS Lambda Function

When working with AWS Lambda, it's important to initialize the Flagship SDK outside the handler function to leverage container reuse, which improves performance for subsequent invocations.

import {
  DecisionMode,
  Flagship,
  LogLevel,
} from "@flagship.io/js-sdk";

/**
 * Access Flagship credentials from environment variables
 */
const { FLAGSHIP_ENV_ID, FLAGSHIP_API_KEY } = process.env;

/**
 * Initialize Flagship SDK with credentials and configuration.
 * IMPORTANT: We initialize outside the handler function to leverage Lambda container reuse.
 */
await Flagship.start(FLAGSHIP_ENV_ID, FLAGSHIP_API_KEY, {
  logLevel: LogLevel.DEBUG,
  // Decision mode can be BUCKETING or DECISION_API
  decisionMode: DecisionMode.BUCKETING,
  // Defer fetching campaign data until explicitly needed
  fetchNow: false,
  // Disable polling for campaigns since Lambda functions are ephemeral
  pollingInterval: 0,
  trackingManagerConfig: {
    // Setting batchIntervals to 0 prevents background timers for analytics
    batchIntervals: 0,
  },
  // Improve performance by sending multiple activate flags in a single request
  batchActivateHits: true,
});

/**
 * AWS Lambda handler function
 */
export const lambdaHandler = async (event, context) => {
  // Your Lambda function logic here
};

Configuration Options

  • decisionMode:

    • BUCKETING makes decisions locally using pre-fetched campaigns (recommended for Lambda)
    • DECISION_API calls Flagship servers for each decision (more network overhead)
  • fetchNow:

    • false defers fetching campaign data until explicitly needed, improving cold start times
  • pollingInterval:

    • 0 disables automatic polling since Lambda functions are ephemeral
  • trackingManagerConfig.batchIntervals:

    • 0 prevents Flagship from using background timers for analytics, which is critical because Lambda may terminate after function completion
  • batchActivateHits:

    • true improves performance by sending multiple activate flags in a single request

Create a visitor object with context from API Gateway events

The visitor object represents a user of your application. In AWS Lambda, you can extract context data from the API Gateway event:

// From the Lambda handler function
const visitorId = event.queryStringParameters?.visitorId;

/**
 * Create a Flagship visitor with contextual data from request headers
 */
const visitor = Flagship.newVisitor({
  visitorId,
  // Set GDPR consent status for data collection
  hasConsented: true,
  context: {
    userAgent: event.headers["user-agent"] || "unknown",
    country: event.headers["cloudfront-viewer-country"] || "unknown",
    path: event.path || "unknown",
    referrer: event.headers["referer"] || "unknown",
    // You can add any additional context that's relevant for targeting
    // isPremiumUser: event.queryStringParameters?.premium === "true",
    // deviceType: detectDeviceType(event.headers["user-agent"]),
  },
});

You can include any information in the context object that might be useful for targeting. API Gateway events provide access to HTTP headers, query parameters, and path information. Common examples include:

  • Demographics: location, language preferences
  • Technical: device type, browser, operating system
  • Behavioral: account type, subscription status
  • Custom: any application-specific attributes

Fetch feature flags assigned to this visitor

Once you have a visitor object, you need to fetch the feature flags assigned to them based on targeting rules:

// Fetch all feature flags assigned to this visitor based on targeting rules
await visitor.fetchFlags();

// Continue with Lambda function logic

This operation evaluates all campaign rules against the visitor's context and assigns flag variations accordingly.

Retrieve specific flag values for use in the application

After fetching flags, you can retrieve specific flag values to control your application's behavior:

// Retrieve specific flag values with default fallbacks
const welcomeMessage = visitor
  .getFlag("welcome_message")
  .getValue("Welcome to our site!");

const isFeatureEnabled = visitor
  .getFlag("new_feature_enabled")
  .getValue(false);

// You can get different types of values:
// Numbers
const discountPercentage = visitor
  .getFlag("discount_percentage")
  .getValue(0);

// Objects
const uiConfig = visitor
  .getFlag("ui_config")
  .getValue({
    theme: "light",
    showBanner: false,
  });

// Arrays
const enabledFeatures = visitor
  .getFlag("enabled_features")
  .getValue(["basic"]);

Always provide a default value that matches the expected type. This ensures your application works even if the flag isn't defined or there's an issue fetching flags.

Note: Calling getValue automatically activates the flag, meaning it will be counted in reporting.

Send analytics data back to Flagship

To measure the impact of your feature flags, send analytics data back to Flagship:

/**
 * Send analytics events back to Flagship for campaign reporting and optimization.
 */
visitor.sendHits([
  {
    type: HitType.PAGE_VIEW,
    documentLocation: event.requestContext?.http?.path || event.path || "unknown",
  },
  {
    type: HitType.EVENT,
    category: EventCategory.ACTION_TRACKING,
    action: "feature_view",
    label: "new_feature",
    value: isFeatureEnabled ? 1 : 0,
  },
]);

Analytics data is crucial for measuring the impact of your feature flags. You can track page views, events, transactions, and more to understand how different flag variations affect user behavior.

Ensure analytics are sent before the Lambda function terminates

AWS Lambda functions can terminate immediately after completion, potentially losing unsent analytics data. To prevent this, call Flagship.close() before returning:

// Ensure all analytics data is sent before the Lambda function terminates
await Flagship.close();

// Return the response
const response = {
  statusCode: 200,
  body: JSON.stringify({
    message: welcomeMessage,
    features: {
      newFeatureEnabled: isFeatureEnabled,
    },
  }),
};

return response;

This ensures that all pending analytics are sent before the Lambda function terminates, giving you accurate reporting data.

Production Considerations

When deploying this integration in production, consider the following best practices:

Security

  1. Environment Variables: Store sensitive information like API keys or ENV ID in environment variables or AWS Secrets Manager.

Cold Starts and Warm Containers

AWS Lambda has the concept of "cold starts" and "warm containers":

  • Cold start: First invocation after deployment or when scaling up
  • Warm container: Subsequent invocations reusing the same container

To optimize performance:

  1. Initialize the Flagship SDK outside the handler function
  2. Use the Bucketing decision mode to minimize network calls
  3. Set fetchNow: false to defer campaign fetching until needed
  4. Consider using Provisioned Concurrency for critical Lambda functions

Learn More