Setting Up Shopify Metafields for Custom Fields
Metafields — mechanism for extending Shopify's standard data model with arbitrary fields without developing an app. Available for products, variants, collections, customers, orders, pages, blogs, and the store itself.
Namespace and Key Concept
Each metafield is identified by a pair namespace.key. Namespace — logical group (usually app name or data domain), key — specific field:
-
custom.delivery_days— custom field "Delivery time" -
specifications.weight_net— characteristics, net weight -
seo.canonical_override— SEO overrides -
loyalty.points_multiplier— loyalty program fields
Standard Shopify namespaces: descriptors (for basic descriptions), facts (actual data).
Metafield Data Types
| Type | Use Cases |
|---|---|
single_line_text_field |
Supplier article, brand, color |
multi_line_text_field |
Extended specifications |
rich_text_field |
Formatted content with HTML |
number_integer |
Quantity, age, year |
number_decimal |
Weight, volume, coefficient |
boolean |
Features: bestseller, new, exclusive |
date |
Manufacturing date, expiration |
date_time |
Event timestamp |
url |
Link to document, video review |
json |
Structured data (specs array) |
color |
Color in HEX (#RRGGBB) |
weight |
Weight with unit |
volume |
Volume with unit |
dimension |
Size with unit |
rating |
Rating with range (min/max) |
file_reference |
File link in media library |
product_reference |
Link to another product |
collection_reference |
Link to collection |
variant_reference |
Link to variant |
page_reference |
Link to page |
mixed_reference |
Link to any resource |
Types *_reference and file_reference can be declared as lists (list.*) to store arrays.
Creating Metafield Definitions via Admin
Admin > Settings > Custom data — interface for creating metafield definitions. Definition fixes data type and makes field available in product/collection cards in Admin.
Without definition, metafield can be created via API, but won't display in Admin UI and won't be available in Liquid (only via Storefront API).
Creation via GraphQL Admin API
// Create metafield definition
const CREATE_DEFINITION = `
mutation metafieldDefinitionCreate($definition: MetafieldDefinitionInput!) {
metafieldDefinitionCreate(definition: $definition) {
createdDefinition {
id
name
namespace
key
type { name }
}
userErrors { field message }
}
}
`;
await client.query({
data: {
query: CREATE_DEFINITION,
variables: {
definition: {
name: "Delivery time (days)",
namespace: "custom",
key: "delivery_days",
type: "number_integer",
ownerType: "PRODUCT",
validations: [
{ name: "min", value: "1" },
{ name: "max", value: "90" }
],
pin: true // Show at top in product card
}
}
}
});
Bulk Populating Metafields
Via Admin API for existing products:
// Set metafields for product
const SET_METAFIELDS = `
mutation metafieldsSet($metafields: [MetafieldsSetInput!]!) {
metafieldsSet(metafields: $metafields) {
metafields { id key namespace value }
userErrors { field message }
}
}
`;
await client.query({
data: {
query: SET_METAFIELDS,
variables: {
metafields: [
{
ownerId: "gid://shopify/Product/123456789",
namespace: "custom",
key: "delivery_days",
type: "number_integer",
value: "3"
},
{
ownerId: "gid://shopify/Product/123456789",
namespace: "specifications",
key: "warranty_years",
type: "number_integer",
value: "2"
}
]
}
}
});
When bulk importing, batch by 25 metafields per request (API limit).
Outputting Metafields in Liquid Theme
Metafield definitions created via Admin are available in Liquid directly:
{%- comment -%} sections/product-specs.liquid {%- endcomment -%}
{%- assign delivery = product.metafields.custom.delivery_days -%}
{%- assign warranty = product.metafields.specifications.warranty_years -%}
{%- assign related = product.metafields.custom.related_products.value -%}
<div class="product-specs">
{%- if delivery != blank -%}
<div class="spec-row">
<span class="spec-label">Delivery time:</span>
<span class="spec-value">{{ delivery.value }} days</span>
</div>
{%- endif -%}
{%- if warranty != blank -%}
<div class="spec-row">
<span class="spec-label">Warranty:</span>
<span class="spec-value">{{ warranty.value }} years</span>
</div>
{%- endif -%}
</div>
{%- comment -%} Related products list (list.product_reference) {%- endcomment -%}
{%- if related != blank -%}
<div class="related-products">
<h3>Also suitable:</h3>
{%- for related_product in related -%}
<a href="{{ related_product.url }}">{{ related_product.title }}</a>
{%- endfor -%}
</div>
{%- endif -%}
For rich_text_field type metafields — output via metafield.value:
{%- assign rich = product.metafields.custom.extended_description -%}
{%- if rich != blank -%}
<div class="product-extended">
{{ rich.value }}
</div>
{%- endif -%}
Metafields via Storefront API (for Headless)
// GraphQL Storefront API
const PRODUCT_WITH_METAFIELDS = `
query productByHandle($handle: String!) {
product(handle: $handle) {
title
metafield(namespace: "custom", key: "delivery_days") {
value
type
}
variants(first: 10) {
edges {
node {
metafield(namespace: "specifications", key: "color_hex") {
value
}
}
}
}
}
}
`;
By default metafields don't return in Storefront API — need to explicitly specify namespace and key or configure Storefront API permissions in Admin.
Metaobjects
More powerful alternative — Metaobjects. Custom content types (like CMS types) with their own fields, usable via reference in product metafields.
Example: create Brand type with fields name, logo, country, description, then use metafield type metaobject_reference pointing to Brand instance.
{%- assign brand = product.metafields.custom.brand.value -%}
{%- if brand -%}
<div class="brand-block">
<img src="{{ brand.fields.logo.value | image_url: width: 120 }}" alt="{{ brand.fields.name.value }}">
<span>{{ brand.fields.name.value }}</span>
<span>{{ brand.fields.country.value }}</span>
</div>
{%- endif -%}
Timeline
Setting up 10–20 metafield definitions with theme output: 1–2 days. Bulk populating metafields for existing catalog (1000–10000 products): 1–3 days including script writing and run. Developing Metaobjects structure for complex catalog (brands, materials, certificates): 3–5 days.







