# Metafields (/docs/build/metafields)



Metafields are custom data attached to Shopify resources: a product, a collection, or the shop itself. They are the right home for any store-specific content your block renders: care instructions, size charts, announcement text. Merchants edit them in Shopify; your block picks up the change with no code deploy.

All metafield reads are asynchronous, so values are `undefined` until they load. Type every result with an interface and null-check before access.

## Which hook to use [#which-hook-to-use]

| Metafields on… | Read with                               | Import from       |
| -------------- | --------------------------------------- | ----------------- |
| a product      | `useMultipleMetafields(handle, fields)` | `@gauntlet/state` |
| a collection   | `useCollectionMetafields(id, fields)`   | `@gauntlet/state` |
| the shop       | `useShopifyQuery(query)`                | `@gauntlet/state` |

`fields` is an array of `{ namespace, key }` descriptors.

## Product metafields [#product-metafields]

`useMultipleMetafields` returns the **values** directly, keyed as `result[namespace][key]`. Define the shape you expect and assert it:

```tsx title="src/app/blocks/product-care.tsx"
import { useMultipleMetafields } from '@gauntlet/state'

interface CareMetafields {
  custom?: {
    care_instructions?: string
    specs?: string
  }
}

const metafields = useMultipleMetafields(productHandle, [
  { namespace: 'custom', key: 'care_instructions' },
  { namespace: 'custom', key: 'specs' },
]) as CareMetafields | undefined

if (!metafields?.custom?.care_instructions) {
  return null
}

let specs: Array<{ label: string; value: string }> = []
try {
  specs = JSON.parse(metafields.custom.specs ?? '[]')
} catch {}
```

The product handle comes from your block's props or the product hooks. One quirk to know: a dash in a namespace becomes an underscore in the result, so a `my-fields` namespace is read as `metafields.my_fields`.

## Collection metafields [#collection-metafields]

Same call shape, but the result holds metafield **nodes**, so read `.value` off each:

```ts
import { useCollectionMetafields } from '@gauntlet/state'

interface FooterMetafields {
  custom?: { footer_text?: { value?: string } }
}

const metafields = useCollectionMetafields(collectionId, [
  { namespace: 'custom', key: 'footer_text' },
]) as FooterMetafields | undefined

const footer = metafields?.custom?.footer_text?.value
if (!footer) return null
```

Only have the handle? `useCollectionMetafieldsByHandle(handle, fields)` takes the same arguments and returns the same shape.

## Shop metafields [#shop-metafields]

There is no dedicated hook. Write a small Storefront query against `shop`, alias each metafield, and run it with `useShopifyQuery`, typed with an interface:

```ts
import { useShopifyQuery } from '@gauntlet/state'

const SHOP_QUERY = `query {
  shop {
    announcement: metafield(namespace: "custom", key: "announcement") { value }
  }
}`

interface ShopResult {
  shop: {
    announcement: { value: string } | null
  }
}

const data = useShopifyQuery<ShopResult>(SHOP_QUERY)

const announcement = data?.shop?.announcement?.value
if (!announcement) return null
```

`useShopifyQuery` returns the data or `null`. There are no separate loading or error flags, and no variables argument; interpolate values into the query string.

## Working with the values [#working-with-the-values]

* A metafield value is **always a string**, numbers included. JSON-typed metafields arrive as a stringified blob, so parse inside `try/catch`; merchant data is not guaranteed valid.
* To resolve a linked resource instead of its id string, add `reference` to the field descriptor: `{ namespace, key, reference: 'image' }` (also `'product'`, `'variant'`). The result is the referenced object.
* Match namespace and key exactly to what is configured in Shopify, minding the dash-to-underscore rule on product results.

## Namespace conventions [#namespace-conventions]

Give your integration its own namespace, usually the integration name, and keep every metafield it reads under it. That prevents collisions with the store's `custom` namespace and with other integrations. It also makes your metafield requirements easy to state in the README: one namespace, a list of keys, and a type for each.
