Shopify metafield definitions can now live in shopify.app.toml instead of a metafieldDefinitionCreate call your app fires on install. You declare each definition under its owner type, run shopify app deploy, and Shopify creates or updates it on every shop that has the app. Values still go through the Admin API. Only the schema moves into config.
The practical gain is deleted code. The install-time bootstrap function goes, and so does the “does this definition exist yet?” query that guards it, because Shopify now applies the schema itself and keeps every shop on the same version. The GraphQL below was checked against Admin API 2026-10.
What does a TOML metafield definition look like?
The table name encodes the owner type, the namespace and the key. [product.metafields.app.care_guide] is a product metafield with namespace $app and key care_guide. The declarative definitions guide lists the keys you can set under it:
# shopify.app.toml
[product.metafields.app.care_guide]
type = "multi_line_text_field"
name = "Care guide"
description = "Washing and storage instructions shown on the product page"
access.admin = "merchant_read_write"
access.storefront = "public_read"
[product.metafields.app.fabric]
type = "single_line_text_field"
name = "Fabric"
validations.choices = ["cotton", "linen", "wool"]
capabilities.admin_filterable = true
[customer.metafields.app.loyalty_tier]
type = "single_line_text_field"
name = "Loyalty tier"
access.customer_account = "read"
access.admin takes merchant_read or merchant_read_write. Leave the merchant on read-only when your app is the only thing that should write the value, which is most sync-style data. access.storefront is public_read or none, and access.customer_account is read, read_write or none.
Validations use dot notation, as in validations.min = "8" or the choices list above. Three capabilities are supported in TOML: admin_filterable, unique_values, and cart_to_order_copyable (order metafields only).
The $app namespace resolves to app--{app_id} on the shop. That matters when you read values from somewhere that does not know which app is asking, such as Liquid. If you want grouping, a table like [product.metafields.analytics.lifetime_value] produces the namespace app--{app_id}--analytics.
How do metaobject definitions work in shopify.app.toml?
Same idea, one level deeper. The definition is [metaobjects.app.<name>] and each field is a table under .fields. The type you query with is $app:<name>, per Shopify’s metaobject definitions guide:
[metaobjects.app.size_chart]
name = "Size chart"
display_name_field = "title"
access.admin = "merchant_read_write"
access.storefront = "public_read"
capabilities.publishable = true
[metaobjects.app.size_chart.fields.title]
name = "Title"
type = "single_line_text_field"
required = true
[metaobjects.app.size_chart.fields.rows]
name = "Rows"
type = "json"
[product.metafields.app.size_chart]
type = "metaobject_reference<$app:size_chart>"
name = "Size chart"
access.storefront = "public_read"
The last block is the useful trick. metaobject_reference<$app:size_chart> points a product metafield at your own metaobject type without you knowing its definition GID, because there is no GID to know until the shop has one.
capabilities.publishable, translatable and renderable all work from TOML. The Online Store capability does not, so a metaobject that needs its own storefront page still needs GraphQL.
Do you still need metafieldDefinitionCreate?
For most apps, no. You need the GraphQL mutations in three cases.
The first is runtime definitions: the merchant decides what fields exist, so you cannot know them at deploy time. The second is data other apps must read or write, which needs a merchant-owned namespace, and TOML only supports the app-reserved $app namespace. The third is a capability TOML cannot express. The guide lists smart collection conditions, pinning and analytics_queryable as unsupported, and warns that capabilities.analytics_queryable “deploys without error but doesn’t activate”. That one will cost someone an afternoon.
Definitions declared in TOML are read-only to the Admin API. Try to update one with metafieldDefinitionUpdate and you get an error back. Change it in the file and deploy instead.
Writing and reading values against the declared schema
Nothing about values changes. You write with metafieldsSet and metaobjectUpsert as before, against the metaobjectUpsert reference:
mutation SetCareGuide {
metafieldsSet(metafields: [{
ownerId: "gid://shopify/Product/1234567890"
namespace: "$app"
key: "care_guide"
type: "multi_line_text_field"
value: "Wash at 30 degrees. Dry flat."
}]) {
metafields { id namespace key }
userErrors { field message code }
}
}
mutation UpsertSizeChart {
metaobjectUpsert(
handle: { type: "$app:size_chart", handle: "mens-tops" }
metaobject: { fields: [
{ key: "title", value: "Men's tops" }
{ key: "rows", value: "[{\"size\":\"M\",\"chest_cm\":100}]" }
] }
) {
metaobject { id handle }
userErrors { field message code }
}
}
Write $app, not app. The bare string is a different namespace from the one your definition lives in.
metafieldsSet takes at most 25 metafields per call, so batch anything bigger. Reading back is unchanged too: product(id:) { careGuide: metafield(namespace: "$app", key: "care_guide") { jsonValue } } on the Admin API.
What breaks: the limits of declarative definitions
Some properties are fixed once a definition exists. The guide’s update table allows name, description, validations and access to change, through TOML or GraphQL. Type, namespace/key and owner type cannot. If rows started life as multi_line_text_field and you now want json, you are adding a new key and migrating values, the same as you would with a GraphQL-created definition.
Then there is the change budget. A single deploy can make at most 25 metafield changes (creates, updates and deletes added together). An app adding forty definitions in one release has to split them across two deploys, so plan the split before release day.
The size caps are per app: 128 metafield definitions per owner type, 32 metaobject definitions, and 64 fields per metaobject.
Deletion is softer than you might assume. Remove a block and redeploy, and Shopify deletes the definition but keeps the values for a while. Put it back before cleanup finishes and it is recreated with a new GID. Uninstall and reinstall does the same. The guide is blunt about the consequence: “Don’t treat metafield definition IDs as stable identifiers across an uninstall and reinstall.” Look definitions up by owner type, namespace and key, or by metaobject type, and never store their GIDs in your database.
Moving an existing app to TOML
If your app already creates definitions in code, Shopify CLI 3.89 or later has shopify app import-custom-data-definitions, announced on the developer changelog in January 2026. It reads the app-owned definitions on your dev store and writes their TOML equivalents. The order Shopify suggests is import, check the result with shopify app dev, ship it with shopify app deploy, and only then delete the bootstrap code.
Remove the bootstrap code in a later release than the TOML deploy, never before it: a shop that installs in between would get no definitions at all. Until then, make sure it treats a TAKEN error on create as success, since the definition now already exists. Afterwards, run the metafieldDefinitions(ownerType: PRODUCT) query from the guide against a production shop and confirm the namespaces and types match before you call the migration done.
This sits alongside the quarterly work in our Shopify API version upgrade checklist. If you are still deciding whether a piece of data should be a metafield or a metaobject in the first place, our metaobjects versus metafields guide covers that choice.
Our recommendation
Move app-owned definitions into shopify.app.toml now, starting with the import command rather than retyping them. Ship it in a release that changes nothing else, so a failed deploy points at one cause. Keep GraphQL definition code only for merchant-configured fields, data other apps share, and the capabilities TOML still ignores, and put a comment beside analytics_queryable if you are tempted to declare it.
We build and maintain Shopify apps, including the unglamorous schema and API-version work that keeps them running on every shop that installs them. If your app’s install flow is still creating definitions by hand, our Shopify development team can move it to TOML with you.