> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Show metafield data on cart lines

> A Cart SDK use case using registerCartEnricher to pull metafields from the Shopify Storefront API onto every cart line, then render them.

The cart knows what Shopify's Ajax API tells it: titles, prices, quantities, properties. It doesn't know your metafields. A **cart enricher** fetches extra product or variant fields from the Storefront API and attaches them to every matching line, so you can show a delivery estimate, a "ships separately" warning, an ingredient list, or anything else you store on the product.

Typical uses: per-product delivery windows, allergen or ingredient badges, a custom "low stock" flag, loyalty point multipliers, subscriber-only pricing.

## Register the enricher

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerCartEnricher({
  id: 'delivery',
  onType: 'Product',
  fragment: `
    deliveryWindow: metafield(namespace: "custom", key: "delivery_window") { value }
    shipsSeparately: metafield(namespace: "custom", key: "ships_separately") { value }
  `,
});
```

Three fields:

| Field      | Description                                                                                                                     |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `id`       | Namespace for the result; it lands at `line.metadata[id]`. Must be unique; a second registration with the same `id` is ignored. |
| `onType`   | `'Product'` or `'ProductVariant'`. Also the join key, so product ID vs. variant ID.                                             |
| `fragment` | A GraphQL field selection (no outer braces) spliced into the Storefront query. Braces must balance.                             |

Whenever the cart loads or changes, Aftersell fetches your fragment for every product or variant in the cart. The fetch is **non-blocking**: the cart renders immediately and re-emits `cart_updated` once the data lands. A slow or failing fragment never delays or breaks the cart.

## Read the data

Because enrichment is asynchronous, `line.metadata.delivery` is `undefined` until the first fetch resolves. Always guard the read.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('cart_updated', (state) => {
  state.items.forEach((line) => {
    const deliveryWindow = line.metadata.delivery?.deliveryWindow?.value;
    if (!deliveryWindow) return; // not fetched yet

    console.log(line.title, 'arrives in', deliveryWindow);
  });
});
```

## Render it in the cart

Enrichment puts the data on the line; a [Custom code block](/aftersell/cart/custom-code-blocks) in React mode draws it. Add the block as a **Cart items sub-block** so it renders once per line and receives that line as `props.line`:

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function CustomCode(props) {
  const data = props.line?.metadata?.delivery;
  const window_ = data?.deliveryWindow?.value;

  if (!window_) return null;

  return (
    <div className="cart-external-custom-code_html" style={{ fontSize: '12px', opacity: 0.7 }}>
      Arrives in {window_}
    </div>
  );
}
```

<Warning>
  Enriched values come back from the Storefront API **as-is and unsanitized**. Render them as text, as above, and never with `dangerouslySetInnerHTML` or by writing raw HTML.
</Warning>

## Variant-level data

Set `onType: 'ProductVariant'` when the metafield lives on the variant rather than the product:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerCartEnricher({
  id: 'pricing',
  onType: 'ProductVariant',
  fragment: `
    anchorPrice: metafield(namespace: "custom", key: "anchor_price") { value }
    subscriberPrice: metafield(namespace: "custom", key: "subscriber_price") { value }
  `,
});
```

## More than metafields

The fragment is spliced into a Storefront API query, so anything the API exposes on a `Product` or `ProductVariant` works, not just metafields:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerCartEnricher({
  id: 'productInfo',
  onType: 'Product',
  fragment: `
    tags
    vendor
    productType
    availableForSale
  `,
});
```

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// Then: warn about anything from a drop-ship vendor.
window.aftersell.cart.events.on('cart_updated', (state) => {
  const hasDropship = state.items.some((line) => {
    const tags = line.metadata.productInfo?.tags;
    return tags?.includes('dropship');
  });
});
```

## Things to get right

* **Guard every read.** `metadata` defaults to `{}` and your namespace is `undefined` until the fetch resolves. The cart renders before the data arrives, always.
* **Each `id` is its own namespace.** Multiple enrichers coexist without colliding, including ones registered by other apps.
* **Metafields must be Storefront-visible.** A metafield that isn't exposed to the Storefront API returns `null`. Check the definition in Shopify admin if you get nothing back.
* **Keep the fragment small.** It runs for every product in the cart, on every cart change. Ask for the fields you use, not everything.
* **Register at set-up time.** It's a hook, so it belongs at the top of your Initialization script.
* **Braces must balance.** No outer braces around the fragment, but any nested selection needs its own matching pair. An unbalanced fragment is rejected.

## Where to go next

* **[`registerCartEnricher`](/aftersell/cart/sdk-hooks#registercartenricher)**: the full hook reference.
* **[Custom code blocks](/aftersell/cart/custom-code-blocks)**: rendering the data.
* **[Cart object](/aftersell/cart/sdk-cart-object)**: where `metadata` sits on a line.
