On this page

JSON payloads

JSON payloads attach dynamic configuration data to experiment variants. Use payloads to change your application's behavior or appearance remotely, without redeploying code.

Strongly typed payloads let you define a schema so Amplitude validates each payload before it reaches your application.

How JSON payloads work

When you create a variant, you can attach a JSON payload that contains any variables you want to pass to your application.

When your application evaluates an experiment:

  • The Experiment SDK fetches the variant for the user.
  • The SDK returns the variant along with its payload.
  • Your application reads the payload and configures the experience dynamically.

This pattern works for simple flags and for complex experiments where the payload defines layouts, feature options, copy, or configuration values.

Workflow overview

A typical workflow follows these steps:

  1. Create a variant with a payload: Add a JSON object to a variant that defines the configuration for that experience.
  2. Activate the flag or experiment: Deploy the configuration to your users.
  3. Evaluate in your application: Use an Experiment SDK (web, mobile, or backend) to fetch the variant for each user.
  4. Use the payload: Read variant.payload and apply the configuration to your UI or business logic.

Create variants with payloads

Add JSON payloads to variants through the Amplitude UI or programmatically through the Management API.

Through the UI

To add a payload when creating or editing a variant:

  1. Navigate to Experiment > Feature Flags or Experiments.
  2. Select the flag or experiment you want to configure.
  3. In the Variants section, create or edit a variant.
  4. Enter the variant name, value, and description.
  5. In the Payload field, add your JSON configuration.
  6. Select Apply, then save your changes.

Example payload – config for a blog layout

json
{
  "layout": "cards",
  "titlePosition": "above",
  "gradient": false,
  "showDescription": true,
  "cardCount": 3
}

For more details on creating variants in the UI, go to Create variations.

Through the Management API

Create variants with payloads using the Experiment Management API. Include the payload field in your request body.

API request example

bash
curl --request POST \
  --url 'https://experiment.amplitude.com/api/1/flags/{id}/variants' \
  --header 'Authorization: Bearer <management-api-key>' \
  --header 'Content-Type: application/json' \
  --data '{
    "key": "cards-layout",
    "name": "Cards Layout",
    "description": "Blog posts displayed in card format",
    "payload": {
      "layout": "cards",
      "titlePosition": "above",
      "gradient": false,
      "showDescription": true,
      "cardCount": 3
    },
    "rolloutWeight": 1
  }'

Refer to the full API docs for:

Access payloads in your application

After you create variants with payloads, your application must:

  1. Initialize the Experiment SDK.
  2. Fetch the variants for the user.
  3. Access variant.payload.
  4. Apply the configuration with sensible defaults.

The exact syntax varies by SDK, but the pattern stays consistent.

JavaScript / TypeScript (browser)

javascript
import { Experiment } from '@amplitude/experiment-js-client';

// Initialize the Experiment client
const experiment = Experiment.initialize('<DEPLOYMENT_KEY>', {
  // Optional configuration
});

// Fetch variants for the current user
await experiment.fetch({
  user_id: 'user123',
  device_id: 'device456',
});

// Get the variant for a specific flag
const variant = experiment.variant('blog-layout-flag');

// Access the payload with defaults
if (variant && variant.payload) {
  const layout = variant.payload.layout || 'list';
  const titlePosition = variant.payload.titlePosition || 'below';
  const showDescription = variant.payload.showDescription !== false;
  const cardCount = variant.payload.cardCount || 5;

  configureBlogLayout({
    layout,
    titlePosition,
    showDescription,
    cardCount,
  });
}

React example

javascript
import { useEffect, useState } from 'react';
import { Experiment } from '@amplitude/experiment-js-client';

function BlogComponent() {
  const [layoutConfig, setLayoutConfig] = useState({
    layout: 'list',        // defaults
    titlePosition: 'below',
    showDescription: true,
    cardCount: 5,
  });

  useEffect(() => {
    async function fetchExperiment() {
      const experiment = Experiment.initialize('<DEPLOYMENT_KEY>');
      await experiment.fetch({ user_id: 'user123' });

      const variant = experiment.variant('blog-layout-flag');
      if (variant && variant.payload) {
        setLayoutConfig({
          layout: variant.payload.layout || 'list',
          titlePosition: variant.payload.titlePosition || 'below',
          showDescription: variant.payload.showDescription !== false,
          cardCount: variant.payload.cardCount || 5,
        });
      }
    }

    fetchExperiment();
  }, []);

  return (
    <BlogLayout
      layout={layoutConfig.layout}
      titlePosition={layoutConfig.titlePosition}
      showDescription={layoutConfig.showDescription}
      cardCount={layoutConfig.cardCount}
    />
  );
}

Server-side example (Node.js)

javascript
import { Experiment } from '@amplitude/experiment-node-server';

const experiment = Experiment.initialize('<DEPLOYMENT_KEY>');

async function getExperimentConfig(userId: string) {
  const user = { user_id: userId };
  const variants = await experiment.fetch(user);
  const variant = variants['blog-layout-flag'];

  if (variant && variant.payload) {
    return {
      layout: variant.payload.layout || 'list',
      titlePosition: variant.payload.titlePosition || 'below',
      showDescription: variant.payload.showDescription !== false,
      cardCount: variant.payload.cardCount || 5,
    };
  }

  // Fallback config
  return {
    layout: 'list',
    titlePosition: 'below',
    showDescription: true,
    cardCount: 5,
  };
}

// Example usage in a route
app.get('/api/blog-config', async (req, res) => {
  const config = await getExperimentConfig(req.user.id);
  res.json(config);
});

Strongly typed JSON payloads (advanced)

JSON payloads are flexible by default: attach any valid JSON to a variant and read it from variant.payload. For more control, define the expected type for variant payloads so Amplitude validates each payload before it reaches your application.

Strongly typed payloads let you:

  • Set the expected type once per flag or experiment.
  • Validate each variant payload against that type in Amplitude.
  • Catch configuration issues, such as wrong types or missing required fields, before they reach production.

JSON payload model

Strongly typed payloads build on the JSON payload model and SDKs this page covers. How you access variant.payload in code doesn't change. You gain stronger guarantees about the payload's shape.

Strongly typed payload use cases

Strong typing is useful when:

  • Multiple teams depend on the same configuration, such as product, engineering, and solutions.
  • Payloads are complex, with nested objects, arrays, and multiple keys.
  • You use flags as remote config or as layers that many experiments share.
  • You want to reduce runtime errors and make configuration safer.

Define the expected type for variant payloads

In the UI, select Set payload type to choose a payload type for the flag or experiment. The type applies to all variants.

Payload type options:

  • None: No type enforcement. Use any valid JSON as a payload.
  • String: variant.payload is a string.
  • Number: variant.payload is a number.
  • Object: variant.payload is an object with no fixed keys or types.
  • Array: variant.payload is an array.
  • Custom Schema: Define an object shape with specific keys and types. Choose the type of each field, such as string, number, boolean, or array, and mark any field as required. Amplitude represents the shape as a JSON schema and validates every variant payload against it.

If a variant payload doesn't match the type you select:

  • The UI shows an error on the variant and indicates the problem, such as a missing required field or an incorrect type.
  • You can't save the change until the payload is valid.
  • Amplitude prevents you from deploying broken configuration.

Type enforcement complements validation in your application code instead of replacing it:

  • Amplitude enforces the payload type when you edit payloads.
  • Your application can validate or narrow types at runtime if needed.

Example: Custom Schema

Suppose you want a payload with headerText required:

json
{
  "buttonColor": "#4A90D9",
  "headerText": "Welcome back!",
  "maxRetries": 3,
  "features": ["dark_mode", "analytics"],
  "showBanner": true
}

When you choose Custom Schema and define these keys and types, Amplitude uses a JSON schema equivalent to:

json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "buttonColor": {
      "type": "string"
    },
    "headerText": {
      "type": "string"
    },
    "maxRetries": {
      "type": "integer"
    },
    "features": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "showBanner": {
      "type": "boolean"
    }
  },
  "required": [
    "features",
    "buttonColor",
    "headerText",
    "maxRetries",
    "showBanner"
  ]
}

Mark any field as required in the Custom Schema. Payloads that omit a required field or use the wrong type for a key fail validation and block saving until you fix them.

Using strongly typed payloads in your code

When you enable strong typing, the SDK interaction stays the same: read payloads from variant.payload and apply your own types in code.

In TypeScript, you can define a matching type:

typescript
type BlogLayoutPayload = {
  layout: 'list' | 'cards' | 'grid';
  titlePosition: 'above' | 'below';
  gradient?: boolean;
  showDescription: boolean;
  cardCount: number;
};

Then cast and use:

typescript
const variant = experiment.variant('blog-layout-flag');

if (variant && variant.payload) {
  const payload = variant.payload as BlogLayoutPayload;

  configureBlogLayout({
    layout: payload.layout,
    titlePosition: payload.titlePosition,
    showDescription: payload.showDescription,
    cardCount: payload.cardCount,
  });
}

The JSON schema in Amplitude enforces this contract when you create or edit payloads. Your application enforces the same contract through types or runtime checks.

Best practices

Follow these best practices to keep your payloads robust and maintainable.

1. Always provide default values

Provide sensible defaults when accessing payload properties. Defaults ensure your application behaves correctly when:

  • The user doesn't receive a variant.
  • The payload is missing expected properties.
  • You turn off the experiment.

Examples:

javascript
const layout = variant?.payload?.layout || 'list';
const cardCount = variant?.payload?.cardCount || 5;
const showDescription = variant?.payload?.showDescription !== false;

2. Validate payload structure

Even with strongly typed payloads, validate payloads in your application:

javascript
function validateLayoutPayload(payload: any): boolean {
  const validLayouts = ['list', 'cards', 'grid'];

  if (!validLayouts.includes(payload.layout)) {
    console.error('Invalid layout in payload:', payload.layout);
    return false;
  }

  if (typeof payload.cardCount !== 'number' || payload.cardCount < 1) {
    console.error('Invalid cardCount in payload:', payload.cardCount);
    return false;
  }

  return true;
}

const variant = experiment.variant('blog-layout-flag');
if (variant?.payload && validateLayoutPayload(variant.payload)) {
  applyLayoutConfig(variant.payload);
}

When using a JSON schema in Amplitude, keep your application-side validation aligned with that schema.

3. Keep payloads simple

Keep payload structures focused on configuration:

  • Avoid large payloads. Aim for less than 10 KB.
  • Avoid deeply nested or highly coupled structures.
  • Avoid sensitive data. Payloads are visible in network traffic and logs.

4. Document your payload schema

Document the expected structure of your payloads, especially when multiple teams touch the same flag or experiment.

Example JSDoc-style documentation:

javascript
/**
 * Blog Layout Flag Payload Schema
 * @typedef {Object} BlogLayoutPayload
 * @property {('list'|'cards'|'grid')} layout         - The layout style
 * @property {('above'|'below')}       titlePosition  - Title position relative to content
 * @property {boolean}                 gradient       - Whether to show gradient backgrounds
 * @property {boolean}                 showDescription- Whether to show post descriptions
 * @property {number}                  cardCount      - Number of cards to display (1-10)
 */

If you're using strongly typed payloads in Amplitude, keep your JSON schema and code documentation or types in sync. Everyone then shares the same contract.

Common use cases

JSON payloads, with or without strong typing, support a wide range of use cases.

Remote configuration

Use payloads to remotely configure features without deploying code:

json
{
  "apiEndpoint": "https://api.v2.example.com",
  "timeout": 5000,
  "retries": 3,
  "enableCache": true
}

UI customization

Configure UI elements like colors, layouts, and text:

json
{
  "primaryColor": "#007AFF",
  "buttonText": "Get Started",
  "showBanner": true,
  "bannerMessage": "Limited time offer!"
}

Feature rollout levels

Gradually expose functionality through configuration flags:

json
{
  "enableAdvancedSearch": true,
  "enableFilters": true,
  "maxResults": 50,
  "showRecommendations": true
}

Content variations

Test different content approaches:

json
{
  "headline": "Transform Your Workflow",
  "subheadline": "Get started in minutes, not hours",
  "ctaText": "Start Free Trial",
  "showTestimonials": true
}

Payload availability

When you access a variant from the SDK or Evaluation API, you can use the value and payload properties:

  • value: the variant's value, such as "on", "off", "control", or "treatment".
  • payload: the JSON configuration you attach.

The Management API and Amplitude UI provide other variant properties like name and description.

For more information about the variant data model, go to Variants.

Was this helpful?