docs/api-reference/store-api/metadata.mdx
The Store API supports storing arbitrary key-value metadata on carts (orders) and line items. Metadata is useful for tracking custom information like gift messages, attribution data, or integration-specific fields.
<Note> Metadata is different from [metafields](/developer/core-concepts/metafields). Metadata is simple JSON storage (untyped, no schema), while metafields are structured, typed, and schema-defined. Use metadata for integration data and internal tracking. Use metafields for customer-facing custom attributes. </Note>You can attach metadata when creating a cart:
<CodeGroup>const cart = await client.carts.create({
metadata: {
utm_source: 'google',
utm_campaign: 'summer_sale',
referral_code: 'FRIEND20',
},
})
curl -X POST 'http://localhost:3000/api/v3/store/carts' \
-H 'X-Spree-Api-Key: pk_xxx' \
-H 'Content-Type: application/json' \
-d '{
"metadata": {
"utm_source": "google",
"utm_campaign": "summer_sale",
"referral_code": "FRIEND20"
}
}'
Update metadata on an existing cart:
<CodeGroup>await client.carts.update(cart.id, {
metadata: {
gift_message: 'Happy Birthday!',
preferred_delivery: 'morning',
},
}, { spreeToken: cart.token })
curl -X PATCH 'http://localhost:3000/api/v3/store/carts/cart_xxx' \
-H 'X-Spree-Api-Key: pk_xxx' \
-H 'X-Spree-Token: <order_token>' \
-H 'Content-Type: application/json' \
-d '{
"metadata": {
"gift_message": "Happy Birthday!",
"preferred_delivery": "morning"
}
}'
Attach metadata to individual items when adding or updating them:
await client.carts.items.create(cart.id, {
variant_id: 'variant_abc123',
quantity: 1,
metadata: {
personalization: 'John',
gift_wrap: true,
},
}, { spreeToken: cart.token })
curl -X POST 'http://localhost:3000/api/v3/store/carts/cart_xxx/items' \
-H 'X-Spree-Api-Key: pk_xxx' \
-H 'X-Spree-Token: <order_token>' \
-H 'Content-Type: application/json' \
-d '{
"variant_id": "variant_abc123",
"quantity": 1,
"metadata": {
"personalization": "John",
"gift_wrap": true
}
}'
Updating metadata merges with existing values rather than replacing them:
<CodeGroup>await client.carts.items.update(cart.id, 'li_xyz789', {
metadata: {
personalization: 'Jane', // Updates existing key
engraving: 'With Love', // Adds new key
},
}, { spreeToken: cart.token })
curl -X PATCH 'http://localhost:3000/api/v3/store/carts/cart_xxx/items/li_xyz789' \
-H 'X-Spree-Api-Key: pk_xxx' \
-H 'X-Spree-Token: <order_token>' \
-H 'Content-Type: application/json' \
-d '{
"metadata": {
"personalization": "Jane",
"engraving": "With Love"
}
}'
Metadata is stored as a flat JSON object. You can use any keys and values:
{
"metadata": {
"string_value": "hello",
"number_value": 42,
"boolean_value": true,
"nested_object": {
"key": "value"
}
}
}
Spree has two permanent, complementary systems for custom data — metadata for machines, metafields for humans.
| Metadata | Metafields | |
|---|---|---|
| Purpose | Developer escape hatch — integration IDs, sync state | Merchant-defined structured attributes |
| Schema | Schemaless JSON — no definition required | Defined via MetafieldDefinitions (typed, validated) |
| Validation | None | Type-specific validation |
| Visibility | Write-only in Store API, readable in Admin API | Configurable (admin-only or public) |
| Admin UI | JSON preview | Dedicated form fields |
| Best for | Integration data, tracking, attribution, write-and-forget | Product specs, custom attributes, customer-facing data |
| API access | Write via Store API, read via Admin API | Read/write via both APIs |
Both systems are here to stay. For more details on metafields, see the Metafields guide.