Shopify development · Custom data

Shopify Metafields Not Showing? A Developer’s Guide to Liquid and Storefront API Access

Trace missing metafields from the saved value to Liquid output, theme settings, and Storefront API access—with practical code examples.

A metafield can exist in Shopify Admin and still appear blank on a product page. It can also render correctly in Liquid while returning null through the Storefront API. These symptoms look similar, but they belong to different parts of the data path.

Investigate the saved value, owning resource, rendering context, and API visibility separately. This guide uses the product metafield custom.material as a consistent example, with additional examples for rich text and product references.

Separate rendering from API access. The definition’s access.storefront setting controls Storefront API access. It does not control whether ordinary metafields render in Liquid templates.[1]

1. Confirm the value exists on the correct resource

A definition describes a field’s structure; it does not populate every product automatically.[1] Open the affected product and confirm that its metafield contains a saved value. Then compare the exact namespace, key, owner type, and product identity with your code.

Product and variant metafields belong to different resources. A value saved against a variant will not appear through a product metafield lookup. Likewise, a definition’s display name is not its key. Copy the identifier from the definition instead of guessing it from a label.

Record one failing product and one working product before editing the theme. This comparison helps expose missing values, different templates, and inconsistent data types.

For app-managed data, identify the app that owns the definition and verify the namespace it actually uses. Creating another field with a similar name can leave existing values attached to the original definition.

2. Read the metafield through the correct Liquid object

Modern Liquid metafields expose properties such as type and value.[2] This example assumes a product context and a single_line_text_field definition:

Liquid · product context · single-line text fieldTheme exampleliquid
{% assign material = product.metafields.custom['material'] %}
{% if material != blank and material.type == 'single_line_text_field' %}
  {% if material.value != blank %}
    <p class="product-material">
      Material: {{ material.value | escape }}
    </p>
  {% endif %}
{% endif %}

The condition avoids rendering an empty paragraph. Escaping the value keeps plain text from becoming unintended HTML. If the block stays empty, inspect the field’s type and the current product before removing the condition.

Inside a product-card snippet, use the product variable actually passed by its caller; it might be card_product, not product. For variant content, read the selected variant’s metafields and ensure your theme updates that content when the selection changes.

Temporarily inspect the resource identifier in an unpublished theme. A nonempty product object can still represent the wrong product, especially when reusable sections render several cards or accept a separately selected product setting.

3. Render according to the metafield type

Rich text, numbers, lists, and references need different handling. Apply metafield_tag to the metafield object when you want Shopify’s supported HTML rendering, rather than applying it to the raw value.[3]

Liquid · custom.care_guide must be a rich_text_fieldTheme exampleliquid
{% assign care_guide = product.metafields.custom['care_guide'] %}
{% if care_guide != blank and care_guide.type == 'rich_text_field' %}
  <div class="product-care-guide">
    {{ care_guide | metafield_tag }}
  </div>
{% endif %}

Match the output to the stored type
Field typeLiquid approach
Plain textRead .value and escape it
Rich textUse metafield_tag
Reference listLoop over resolved objects in .value

Do not assume every list supports the same filter. For boolean fields, distinguish a stored false value from an absent metafield instead of hiding both with a truthiness test.

4. Verify the theme template and dynamic-source connection

A populated metafield does not automatically create a storefront element. Confirm that the affected product uses the template you edited and that the relevant section or block is present and visible.

Dynamic sources connect compatible resource data to section or block settings. Their availability depends on the template context and setting type.[4] A missing option in the picker can therefore indicate an incompatible setting, rather than missing data.

Compare the previewed theme with the published theme. If HTML contains the value but visitors cannot see it, inspect CSS visibility and collapsed interface states before changing metafield permissions.

5. Inspect Admin data before changing Storefront access

Use a server-side Admin API client with the required product access. Supply the affected product’s actual GraphQL ID as productId. This query checks the value and its definition’s Storefront visibility:[5]

Admin GraphQL · inspect a product’s saved metafieldRead-only querygraphql
# POST /admin/api/2026-10/graphql.json
# Variables: {"productId":"gid://shopify/Product/1234567890"}
# Replace the example ID with the actual product ID.
query InspectMaterial($productId: ID!) {
  product(id: $productId) {
    id
    title
    metafield(namespace: "custom", key: "material") {
      namespace
      key
      type
      value
      definition {
        access {
          storefront
        }
      }
    }
  }
}

Admin visibility does not prove Storefront visibility. Customer-facing metafields need the definition’s Storefront access set to PUBLIC_READ. For an existing merchant-owned definition, this Admin mutation targets its namespace, key, and owner type using the 2026-10 input structure.[6]

Admin GraphQL · expose the existing custom.material definitionChanges visibilitygraphql
# POST /admin/api/2026-10/graphql.json
# Changes visibility for all values covered by this definition.
mutation ExposeProductMaterial {
  metafieldDefinitionUpdate(
    definition: {
      namespace: "custom"
      key: "material"
      ownerType: PRODUCT
      access: { storefront: PUBLIC_READ }
    }
  ) {
    updatedDefinition {
      namespace
      key
      access {
        storefront
      }
    }
    userErrors {
      field
      message
      code
    }
  }
}

This changes public API visibility for values covered by the definition. Apply it only to information intended for shoppers. Inspect userErrors and top-level GraphQL errors, and retain Admin credentials exclusively on the server.

6. Query the Storefront API independently

Send this query to the Storefront endpoint with appropriate Storefront authentication, not the Admin endpoint. Supply the product handle as handle and request the exact metafield explicitly.[7]

Storefront GraphQL · use the same shop and actual product handleRead-only querygraphql
# POST /api/2026-10/graphql.json
# Variables: {"handle":"linen-shirt"}
query ReadProductMaterial($handle: String!) {
  product(handle: $handle) {
    id
    title
    metafield(namespace: "custom", key: "material") {
      type
      value
    }
  }
}

Interpret the response before editing the theme
Where does the expected data disappear?
Product is null
Verify shop, handle, and channel availability
Metafield is null
Verify owner, saved value, identifiers, and access
Value is returned
Inspect application mapping and rendering

Read the response’s errors alongside data. A successful HTTP response is not enough to establish that the requested field resolved correctly.

Keep the shop, product, and API version consistent when comparing Admin and Storefront responses. Save the returned type alongside the value so your frontend can distinguish plain text from serialized structured content.

7. Resolve references instead of displaying raw identifiers

The Storefront API returns metafield value as a string. Resource references can be expanded using reference or references.[8] For a list.product_reference field, query the linked products directly:

Storefront GraphQL · custom.related_products is a list.product_referenceRead-only querygraphql
# POST /api/2026-10/graphql.json
# First page variables: {"handle":"linen-shirt","after":null}
query ReadRelatedProducts($handle: String!, $after: String) {
  product(handle: $handle) {
    related: metafield(namespace: "custom", key: "related_products") {
      type
      references(first: 10, after: $after) {
        nodes {
          ... on Product {
            id
            title
            handle
          }
        }
        pageInfo {
          hasNextPage
          endCursor
        }
      }
    }
  }
}

This example fetches ten references and exposes pagination information. Follow subsequent pages when needed. Verify that linked products are accessible in the same storefront context; a stored identifier alone is not displayable product content.

Repeat the definition-access investigation for this separate field. Publishing the material definition does not automatically expose related products. Handle empty reference results without leaving broken links or empty cards.

8. Validate the result across real storefront contexts

Repeat the original failing case after one targeted change. Test a populated field, an absent value, and the relevant variant or template. Remove temporary diagnostics and confirm that public output contains only intended customer-facing information.

If the API returns the updated value but the interface remains stale, inspect your application’s data transformation and caching. Preserve a short explanation of the failed layer and the successful verification so the same issue is easier to diagnose next time.