JSON payloads let you attach dynamic configuration data to experiment variants. This enables you to remotely change your application's behavior or appearance without redeploying code.
With strongly typed payloads, you can define a schema for those payloads so Amplitude can validate them before they reach your application.
When you create a variant, you can optionally attach a JSON payload containing any variables you want to pass to your application.
When your application evaluates an experiment:
This works for both simple flags and more complex experiments where the payload defines layouts, feature options, copy, or configuration values.
A typical workflow looks like this:
variant.payload and apply the configuration to your UI or business logic.You can add JSON payloads to variants through the Amplitude UI or programmatically through the Management API.
To add a payload when creating or editing a variant:
{
"layout": "cards",
"titlePosition": "above",
"gradient": false,
"showDescription": true,
"cardCount": 3
}
For more details on creating variants in the UI, go to Create variations.
You can also create variants with payloads using the Experiment Management API. Include the payload field in your request body.
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:
After you create variants with payloads, your application needs to:
variant.payload.The exact syntax varies by SDK, but the pattern is consistent.
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,
});
}
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}
/>
);
}
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);
});
JSON payloads are flexible by default: you can attach any valid JSON to a variant and read it from variant.payload. For more control, you can define the expected type for variant payloads so Amplitude validates each payload before it reaches your application.
Strongly typed payloads help you:
variant.payload in code doesn't change, you just gain stronger guarantees about what that payload looks like.Strong typing is especially useful when:
In the UI, by clicking Set payload type, you choose a payload type for the flag or experiment. The type applies to all variants.
Payload type options:
variant.payload is a string.variant.payload is a number.variant.payload is an object (no fixed keys or types).variant.payload is an array.If a variant payload doesn't match the type you select:
This complements validation in your application code instead of replacing it:
Suppose you want a payload like this, with headerText required:
{
"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:
{
"$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"
]
}
You can mark any of the fields 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.
Even when you enable strong typing, the SDK interaction stays the same: you still read payloads from variant.payload and apply your own types in code.
In TypeScript, you can define a matching type:
type BlogLayoutPayload = {
layout: 'list' | 'cards' | 'grid';
titlePosition: 'above' | 'below';
gradient?: boolean;
showDescription: boolean;
cardCount: number;
};
Then cast and use:
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 ensures payloads conform to this contract when you create or edit them, and your application enforces the same contract through types or runtime checks.
Follow these best practices to keep your payloads robust and maintainable.
Always provide sensible defaults when accessing payload properties. This ensures your application behaves correctly if:
Examples:
const layout = variant?.payload?.layout || 'list';
const cardCount = variant?.payload?.cardCount || 5;
const showDescription = variant?.payload?.showDescription !== false;
Even with strongly typed payloads, validate payloads in your application:
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, try to keep your application-side validation aligned with that schema.
Keep payload structures focused on configuration:
Document the expected structure of your payloads, especially when multiple teams touch the same flag or experiment.
Example JSDoc-style documentation:
/**
* 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 your code documentation or types in sync so everyone shares the same contract.
JSON payloads (with or without strong typing) support a wide range of use cases.
Use payloads to remotely configure features without deploying code:
{
"apiEndpoint": "https://api.v2.example.com",
"timeout": 5000,
"retries": 3,
"enableCache": true
}
Configure UI elements like colors, layouts, and text:
{
"primaryColor": "#007AFF",
"buttonText": "Get Started",
"showBanner": true,
"bannerMessage": "Limited time offer!"
}
Gradually expose functionality through configuration flags:
{
"enableAdvancedSearch": true,
"enableFilters": true,
"maxResults": 50,
"showRecommendations": true
}
Test different content approaches:
{
"headline": "Transform Your Workflow",
"subheadline": "Get started in minutes, not hours",
"ctaText": "Start Free Trial",
"showTestimonials": true
}
When you access a variant from the SDK or Evaluation API, you can use the value and payload properties:
value: the variant's value (for example, "on," "off," "control," or "treatment").payload: the JSON configuration you attach.The Management API or Amplitude UI provides other variant properties like name and description.
For more information about the variant data model, go to Variants.
November 19th, 2024
Need help? Contact Support
Visit Amplitude.com
Have a look at the Amplitude Blog
Learn more at Amplitude Academy
© 2026 Amplitude, Inc. All rights reserved. Amplitude is a registered trademark of Amplitude, Inc.