Metafields
Metafields let you attach typed, namespaced data to nine resource kinds — products, variants, collections, customers, orders, pages, blogs, articles, and the shop itself. They are strongly-typed, server-validated, stored in a separate polymorphic table, and exposed in Aqua/Liquid via the standard{owner}.metafields.{namespace}.{key} access pattern.
Use them to:
- attach app-specific data to a customer, order, or product (e.g. loyalty tier, subscription status, AI-generated badge text)
- expose structured fields to merchants under Settings → Custom data that they can fill in per resource and reference in their theme
- store references between resources (
product_reference,collection_reference,file_reference) so themes can link related items
Apps store all per-store runtime data here. The platform does not give
apps a filesystem to write to — install config, feature toggles, per-resource
counters, event logs, etc. all live as metafields under an
app_<handle_with_underscores> namespace. See
Where to store app data in the
Extensions overview for the conventions.Two API contexts
Metafields are exposed through two parallel REST surfaces with different auth and capabilities.| Context | Base path | Auth | Use case |
|---|---|---|---|
| App API (this doc) | /api/v1/... | OAuth scopes read_metafields / write_metafields | Apps reading/writing their own metafields |
| Admin API | /metafields..., /metafield-definitions... | Merchant JWT | Admin UI, bulk save, definition management |
appId). The
Admin API is what powers the admin’s Settings → Custom data and the
inline editor on resource detail pages — apps cannot use it.
| Operation | App API (OAuth) | Admin API (Merchant JWT) |
|---|---|---|
| List metafields | ✓ GET /api/v1/metafields.json | ✓ GET /metafields |
| Read by id | – | ✓ GET /metafields/:id |
| Upsert one | ✓ POST /api/v1/metafields.json | ✓ POST /metafields |
| Bulk upsert (N at once) | – | ✓ POST /metafields/bulk |
| Update by id | ✓ PUT /api/v1/metafields/:id.json | ✓ PUT /metafields/:id |
| Delete by id | ✓ DELETE /api/v1/metafields/:id.json | ✓ DELETE /metafields/:id |
| Definitions CRUD | – | ✓ /metafield-definitions/... |
Owner types (nine)
TheownerType field on every metafield identifies the kind of resource it
attaches to. Validation is case-sensitive lowercase — sending "PRODUCT"
returns a validation error.
ownerType | Resource | Aqua/Liquid drop |
|---|---|---|
shop | Account / store | shop.metafields |
product | Product | product.metafields |
variant | Product variant | variant.metafields |
collection | Category | collection.metafields |
customer | Client | customer.metafields |
order | Order | order.metafields |
page | Page | page.metafields |
blog | Blog | blog.metafields |
article | Article (blog post) | article.metafields |
Supported types (22)
Each metafield carries atype that determines validation, decoding, and
how it renders in Aqua/Liquid.
Text
| Type | Validations | {{ mf }} renders |
|---|---|---|
single_line_text_field | min, max, regex, choices | string |
multi_line_text_field | min, max, regex | string |
rich_text_field (alias rich_text) | – | raw HTML, unescaped |
Numeric / Boolean / Color
| Type | Validations | Renders |
|---|---|---|
number_integer (alias integer) | min, max | integer toString |
number_decimal | min, max, max_precision | decimal toString |
boolean | accepts true/false/"true"/"false"/0/1 | "true" / "false" |
color | 6-char hex #RRGGBB only | "#RRGGBB" |
Date / Time
| Type | Format | Validations |
|---|---|---|
date | ISO 8601 YYYY-MM-DD | min, max (date strings) |
date_time | ISO 8601 with optional time + timezone | min, max |
Measurements
Stored as{ unit, value } objects. Renders as "<value> <unit>".
| Type | Allowed unit values |
|---|---|
weight | KILOGRAMS, GRAMS, POUNDS, OUNCES |
dimension | METERS, CENTIMETERS, MILLIMETERS, INCHES, FEET, YARDS |
volume | MILLILITERS, CENTILITERS, LITERS, CUBIC_METERS, FLUID_OUNCES, PINTS, QUARTS, GALLONS (+ IMPERIAL_* variants) |
Money / Rating
| Type | Shape | Notes |
|---|---|---|
money | { amount: "12.50", currency_code: "USD" } | currency must be ISO-4217. Pipe through | money in Liquid. |
rating | { scale_min, scale_max, value } | scale_max > scale_min, scale_min ≤ value ≤ scale_max |
URL / JSON
| Type | Notes |
|---|---|
url | parsed via new URL(). Default schemes https, http, mailto, tel. Override with validations.allowed_schemes. |
json (alias json_string) | any JSON-serialisable value |
References
Stored as id strings; the backend optionally verifies the id exists at write time.product_reference, variant_reference, collection_reference,
customer_reference, page_reference, file_reference
Lists
list.<innerType> — array of items of innerType. Inner-type validations
apply to each item.
| Validation | Effect |
|---|---|
list_min / list_max | array length bounds |
| inner-type rules | applied to each item |
Wire format
Compound types are sent as JSON in thevalue field:
Validation errors
Type or validation failures return HTTP 400 with a structurederrors array:
Cache invalidation
Writes to a metafield automatically expire the relevant storefront caches — both the cached copy of the owning resource and any cached page HTML that rendered it. The next storefront render will see the new value. You don’t need to poll — if you need to react to changes, register ametafields/update
webhook instead.
Best practices for apps
- Use your own namespace. Don’t write under
custom(the merchant namespace). Use your app handle (e.g.subscriptions_pro,loyalty_engine) so merchants can see what’s yours and apps don’t collide on key names. - Pre-create definitions during app install. Apps can
POST /metafield-definitions(with merchant scope, not OAuth) on first install if you want the schema to appear in the merchant admin. For OAuth-only apps, write values directly without defining first. - Pin definitions that merchants need to fill in — they show up on the resource detail page in admin.
- Don’t poll. Use webhooks.