Ready to maximize your revenue?Book a Demo
TOPCODE
Metafields

How to add a metafield definition via the Shopify CLI

Last updated on June 2, 2026

How to add a metafield definition via the Shopify CLI

TOPCODE

Metafield definitions are the foundation of structured custom data in Shopify. Before definitions were introduced, metafields were essentially a freeform key-value store with no type enforcement. With definitions, you declare the exact data type, validation rules, and display name for every custom field you attach to products, variants, customers, orders, or collections. The Shopify CLI makes it possible to version-control your definitions as code and apply them repeatably across development, staging, and production stores.

What You'll Build

You will define two metafield definitions for the product resource: a single_line_text_field for a care instructions label and a list.product_reference for related products. You'll create these using the Admin GraphQL API from a CLI-run script, then verify the definitions appear in the Shopify admin.

Before You Start

You need Shopify CLI 3.50+ installed (shopify version), a development store, and an app installed on that store with the write_products scope (which grants access to create metafield definitions on the product owner). Alternatively, you can use a custom app with a direct API token from the store admin — this tutorial uses the shopify app generate scaffolded app as the starting point.

1

Understand metafield types

Shopify supports over 20 metafield types. The most commonly used are: single_line_text_field (short text, validated against an optional regex), multi_line_text_field (plain text with newlines), rich_text_field (HTML-safe rich text stored as JSON), json (arbitrary JSON blob), number_integer, number_decimal, boolean, date, url, color, and reference types like product_reference, page_reference, metaobject_reference. List variants are prefixed with list. (e.g. list.product_reference) and store multiple values.

Every definition belongs to a namespace and key. Convention for app-owned definitions is to use a reversed-domain namespace like com.myapp or a short app slug. Avoid the global namespace — it is reserved for merchant-created definitions in the Shopify admin.

Expected outcome: You understand the type system and naming conventions before writing any mutations.

2

Write the metafieldDefinitionCreate GraphQL mutation

Metafield definitions are created via the Admin GraphQL API's metafieldDefinitionCreate mutation. The required fields are name, namespace, key, type, and ownerType. The ownerType enum accepts PRODUCT, PRODUCTVARIANT, CUSTOMER, ORDER, COLLECTION, LOCATION, and several others.

mutations/createMetafieldDefinition.graphqlgraphql
# mutations/createMetafieldDefinition.graphql
mutation MetafieldDefinitionCreate($definition: MetafieldDefinitionInput!) {
  metafieldDefinitionCreate(definition: $definition) {
    createdDefinition {
      id
      name
      namespace
      key
      type {
        name
      }
      ownerType
    }
    userErrors {
      field
      message
      code
    }
  }
}

Expected outcome: You have a reusable mutation document you can run with different variable sets for each definition.

3

Create a seed script

Create a standalone Node.js script that runs the mutation twice — once for each definition. The script should be idempotent: if a definition with the same namespace/key already exists, Shopify returns a TAKEN user error — catch and ignore that specific code so the script doesn't fail on repeated runs.

Run the script via shopify app function run — or just node scripts/seedMetafields.ts with tsx if your project uses it. Pass the shop domain and access token as environment variables so the script works against any store without modifying code.

scripts/seedMetafields.tsts
// scripts/seedMetafields.ts
import { createAdminApiClient } from '@shopify/admin-api-client';

const client = createAdminApiClient({
  storeDomain: process.env.SHOPIFY_SHOP_DOMAIN!,
  apiVersion: '2024-01',
  accessToken: process.env.SHOPIFY_ACCESS_TOKEN!,
});

const MUTATION = `
  mutation MetafieldDefinitionCreate($definition: MetafieldDefinitionInput!) {
    metafieldDefinitionCreate(definition: $definition) {
      createdDefinition { id name }
      userErrors { field message code }
    }
  }
`;

const definitions = [
  {
    name: 'Care Instructions',
    namespace: 'myapp',
    key: 'care_instructions',
    type: 'single_line_text_field',
    ownerType: 'PRODUCT',
    description: 'Short care label text, e.g. "Machine wash cold".',
  },
  {
    name: 'Related Products',
    namespace: 'myapp',
    key: 'related_products',
    type: 'list.product_reference',
    ownerType: 'PRODUCT',
    description: 'Up to 10 products to show in the related products section.',
  },
];

async function seed() {
  for (const definition of definitions) {
    const { data, errors } = await client.request(MUTATION, { variables: { definition } });
    const userErrors = data?.metafieldDefinitionCreate?.userErrors ?? [];
    const taken = userErrors.find((e: { code: string }) => e.code === 'TAKEN');
    if (taken) {
      console.log(`Definition ${definition.namespace}.${definition.key} already exists — skipping.`);
      continue;
    }
    if (errors || userErrors.length > 0) {
      console.error('Error:', errors ?? userErrors);
      process.exit(1);
    }
    console.log(`Created: ${data.metafieldDefinitionCreate.createdDefinition.id}`);
  }
}

seed();

Expected outcome: Running `npx tsx scripts/seedMetafields.ts` creates both definitions. Run it a second time — it should print 'already exists' without error.

4

Set a metafield value on a product

With the definition in place, you can now write metafield values using the productUpdate mutation's metafields argument. Each entry requires namespace, key, value, and type. For list.product_reference, value must be a JSON-encoded array of product GIDs, e.g. ["gid://shopify/Product/123", "gid://shopify/Product/456"] as a string.

mutation SetProductMetafields($input: ProductInput!) {
  productUpdate(input: $input) {
    product {
      id
      metafields(first: 5, namespace: "myapp") {
        edges {
          node {
            key
            value
          }
        }
      }
    }
    userErrors { field message }
  }
}

# Variables:
# {
#   "input": {
#     "id": "gid://shopify/Product/1234567890",
#     "metafields": [
#       { "namespace": "myapp", "key": "care_instructions",
#         "value": "Machine wash cold, tumble dry low", "type": "single_line_text_field" },
#       { "namespace": "myapp", "key": "related_products",
#         "value": "[\"gid://shopify/Product/111\",\"gid://shopify/Product/222\"]",
#         "type": "list.product_reference" }
#     ]
#   }
# }

Expected outcome: The product now has metafield values. You can read them back via the storefront API using `product.metafield(namespace: "myapp", key: "care_instructions")` in any Liquid or Hydrogen template.

5

Verify definitions in the Shopify admin

Navigate to your development store admin → Settings → Custom data → Products. Your two definitions — "Care Instructions" and "Related Products" — should appear in the list under the myapp namespace group. You can also confirm the type column shows the correct type. Click into any definition to see the full details, including the description text you provided.

To also verify via GraphQL, you can query metafieldDefinitions(ownerType: PRODUCT, namespace: "myapp") from the Admin API. The Shopify CLI ships with a GraphQL explorer accessible via shopify app graphiql — use it to quickly verify the definition IDs without writing a fetch script.

Expected outcome: Both definitions are visible in the admin settings and via the GraphQL explorer.

What's Next

Once your definitions exist, consider surfacing the metafield values in a Hydrogen storefront using a GraphQL fragment on Product, or add an admin UI extension that lets merchants edit the care_instructions field directly from the product detail page. You can also add validation rules to the single_line_text_field definition (e.g., min/max character length) by passing a validations array in the MetafieldDefinitionInput.

Senior Shopify Engineer

Frances Chen

Keep reading