docs/developer/core-concepts/metafields.mdx
import { Since } from '/snippets/since.mdx';
<Since version="5.2" />Metafields provide a flexible, type-safe system for adding custom structured attributes to Spree models. Unlike metadata which is simple JSON storage, metafields are schema-defined with strong typing, validation, and visibility controls.
Use metafields for:
erDiagram
MetafieldDefinition ||--o{ Metafield : "defines schema for"
Product ||--o{ Metafield : "has many"
Variant ||--o{ Metafield : "has many"
Order ||--o{ Metafield : "has many"
MetafieldDefinition {
string namespace
string key
string name
string metafield_type
string resource_type
string display_on
boolean searchable
boolean sortable
}
Metafield {
string type
text value
string resource_type
}
flowchart LR
subgraph Definition
A[MetafieldDefinition] --> B["namespace: properties"]
A --> C["key: manufacturer"]
A --> D["type: ShortText"]
A --> E["resource_type: Product"]
A --> F["display_on: both"]
end
subgraph Instance
G[Metafield] --> H["value: Wilson"]
G --> I["resource: Product #1"]
end
A -.->|"defines schema"| G
| Type | Description | Example Values |
|---|---|---|
| Short Text | Brief text fields | SKU codes, brand names, tags |
| Long Text | Longer text content | Care instructions, notes |
| Rich Text | Formatted HTML content | Product descriptions with formatting |
| Number | Numeric values | Weight, quantity, ratings |
| Boolean | True/false flags | Is featured, requires signature |
| JSON | Structured data | Configuration, complex objects |
Metafields support two visibility levels via the display_on attribute:
| Visibility | Store API | Admin API | Use Case |
|---|---|---|---|
both | Yes | Yes | Public product specifications |
back_end | No | Yes | Internal notes, integration IDs |
Product metafield definitions can opt into SearchProvider participation:
| Flag | Effect | Allowed field types |
|---|---|---|
searchable | Values are included in product text search | short_text, long_text, number |
sortable | Listings can sort with sort=cf_{namespace}_{key} (prefix - for descending), e.g. cf_custom_label | short_text, number |
Both default to false. Set them on the definition (Admin UI, Admin API, or seeds). For an existing catalog with Meilisearch, run rake spree:search:reindex after toggling flags so index settings and product documents pick up new cf_* fields.
Any definition with either flag set also becomes filterable on product listings, using its cf_* key as a Ransack predicate. The Store and Admin APIs accept the same keys and the same sort values:
GET /api/v3/store/products?q[cf_custom_material_i_cont]=wool
GET /api/v3/admin/products?q[cf_custom_weight_gteq]=3.5
GET /api/v3/store/products?sort=-cf_custom_weight
| Field type | Predicates |
|---|---|
short_text, long_text | i_cont, cont, eq, not_eq, start, end, present, blank |
number | eq, gt, gteq, lt, lteq, present, blank |
Use i_cont for case-insensitive matching — plain cont is case-sensitive on PostgreSQL.
Predicates that don't match the definition's field type are ignored rather than rejected, as are keys matching no definition. Meilisearch supports the equality, range, and present/blank predicates; substring predicates (i_cont, cont, start, end) apply only to the database provider.
CSV exports honor the same predicates, so exporting a filtered product list matches what the admin sees.
In the admin dashboard, these fields appear in the products table's column selector, Sort dropdown, and filter panel — hidden by default, so admins opt in per column.
Metafields can be attached to most Spree resources including Products, Variants, Orders, Line Items, Taxons, Payments, Shipments, Gift Cards, Store Credits, and more.
<Info> Custom resources can also support metafields. See the [Customization Quickstart](/developer/customization/quickstart) for details. </Info>Namespaces organize metafields into logical groups and prevent key conflicts:
| Namespace | Example Keys | Purpose |
|---|---|---|
properties | manufacturer, material, fit | Product specifications |
shopify | product_id, variant_id | Integration data |
flags | featured, requires_approval | Feature flags |
custom | gift_message, delivery_notes | Business-specific fields |
Metafields with display_on set to both are included in Store API responses when you request the custom_fields expand:
const product = await client.products.get('spree-tote', {
expand: ['custom_fields'],
})
product.custom_fields?.forEach(field => {
console.log(field.key) // "properties.manufacturer"
console.log(field.label) // "Manufacturer"
console.log(field.value) // "Wilson"
console.log(field.field_type) // "short_text"
})
const product = await adminClient.products.get('prod_86Rf07xd4z', {
expand: ['custom_fields'],
})
curl 'https://api.mystore.com/api/v3/store/products/spree-tote?expand=custom_fields' \
-H 'X-Spree-Api-Key: pk_xxx'
Response:
{
"id": "prod_86Rf07xd4z",
"name": "Spree T-Shirt",
"custom_fields": [
{
"id": "cf_k5nR8xLq",
"label": "Manufacturer",
"key": "properties.manufacturer",
"field_type": "short_text",
"value": "Wilson"
},
{
"id": "cf_m3Rp9wXz",
"label": "Material",
"key": "properties.material",
"field_type": "short_text",
"value": "100% Cotton"
}
]
}
Navigate to Settings → Metafield Definitions in the Admin Panel to create and manage metafield definitions. Select the resource type, enter namespace and key, choose the data type, and set visibility.
Definitions are also managed via the Admin API. storefront_visible: true is equivalent to display_on: both — it exposes the field to the Store API:
import { createAdminClient } from '@spree/admin-sdk'
const client = createAdminClient({
baseUrl: 'https://store.example.com',
secretKey: 'sk_xxx',
})
const definition = await client.customFieldDefinitions.create({
resource_type: 'Spree::Product',
namespace: 'properties',
key: 'manufacturer',
label: 'Manufacturer',
field_type: 'short_text',
storefront_visible: true,
})
await client.customFieldDefinitions.update(definition.id, { storefront_visible: false })
await client.customFieldDefinitions.delete(definition.id)
spree api post /custom_field_definitions -d '{
"resource_type": "Spree::Product",
"namespace": "properties",
"key": "manufacturer",
"label": "Manufacturer",
"field_type": "short_text",
"storefront_visible": true
}'
When editing a resource (e.g., a product), metafields appear in a dedicated section. The admin panel automatically builds forms for all defined metafields.
To set a value programmatically, use the resource's nested customFields accessor (parent ID first):
await client.products.customFields.create('prod_xxx', {
custom_field_definition_id: 'cfdef_xxx',
value: 'Wilson',
})
spree api post /products/prod_xxx/custom_fields -d '{
"custom_field_definition_id": "cfdef_xxx",
"value": "Wilson"
}'
Spree has two permanent, complementary systems for custom data — metadata for machines, metafields for humans. They serve different purposes and are not interchangeable. Neither is going away.
| Feature | Metafields | Metadata |
|---|---|---|
| Purpose | Merchant-defined structured attributes | Developer escape hatch — integration IDs, sync state |
| Schema | Defined via MetafieldDefinitions | Schemaless JSON — no definition required |
| Validation | Type-specific (text, number, boolean, etc.) | None — accepts any JSON-serializable data |
| Visibility | Configurable (admin-only or public) | Write-only in Store API, readable in Admin API |
| Admin UI | Dedicated management forms | JSON preview |
| Data Types | 6 specific types | Any JSON value |
| Organization | Namespaced (namespace.key) | Flat key-value structure |
| Queryable | Via SQL joins, Ransack scopes, search providers | Via JSONB operators (PostgreSQL) |
Use Metafields when you need type validation, visibility control, admin UI forms, or organized namespacing.
Use Metadata for external system IDs, tracking attribution, syncing with integrations, or simple write-and-forget data that only backend systems need to read.
<Warning> Product Properties are deprecated and will be removed in Spree 6.0. For new projects, always use Metafields. For existing projects, plan to migrate using the [migration guide](/developer/upgrades/5.1-to-5.2#migrate-to-metafields-or-keep-using-product-properties). </Warning>