Skip to main content

Metafield

Description

Renders the value of a single metafield from an entity you supply. The component is purely presentational: it performs no data fetching and reads no entity contexts, which makes it usable anywhere the data is available — a product detail page, a category grid, a related-products shelf, or the footer.

Its only ambient input is the active theme's declaration of the field, projected into the app context from the theme's theme.json. That declaration supplies the field's display name, its placeholder default, and its customer-visibility flag.

Import

import { Metafield } from '@components/common/metafield/index.js';

Usage

import { Metafield } from '@components/common/metafield/index.js';
import { useProduct } from '@components/frontStore/catalog/ProductContext.js';

export default function ProductMaterial() {
const product = useProduct();

return (
<Metafield
owner="product"
entity={product}
namespace="mytheme"
fieldKey="material"
as="p"
className="product__material"
/>
);
}

export const layout = { areaId: 'productNameAfter', sortOrder: 10 };

Props

NameTypeDefaultDescription
ownerstring-Owner entity type (required). product, category, collection, customer, order, shop, blog_post, blog_category — an open set.
namespacestring-The field's namespace (required).
fieldKeystring-The field's key (required).
entity{ uuid?, metafields? } | null | undefined-Required. The entity object carrying the metafields selection. There is no context fallback — see below.
defaultValueany-Per-call-site fallback rendered when the value is unset. Takes precedence over the declaration's placeholder.
render(value, meta) => ReactNode-Custom renderer. Required for anything that is not a string or a number.
askeyof JSX.IntrinsicElements'span'Tag used by the built-in scalar renderer. Ignored when render is supplied.
classNamestring-Class applied by the built-in scalar renderer. Ignored when render is supplied.

entity is required

<Metafield> never looks up the entity for you. It does not call useProduct(), it does not read a category or collection context, and it issues no query of its own. You pass the object, and the object must carry the field's data.

// Detail page — pass the page's entity.
const product = useProduct();
<Metafield owner="product" entity={product} namespace="mytheme" fieldKey="material" />

// Grid or shelf — no per-item context exists, so pass each item.
{products.map((item) => (
<Metafield key={item.uuid} owner="product" entity={item}
namespace="mytheme" fieldKey="material" />
))}

// Shop-level field — pass the setting object from the enclosing master's query.
<Metafield owner="shop" entity={setting} namespace="mytheme" fieldKey="tagline" />

A nullish entity is tolerated at runtime — the resolution simply falls through to the default — so loading states and optional data do not crash the page.

For this to produce anything, the page's GraphQL query must select the field data:

currentProduct {
uuid
metafields { namespace key type value }
}

The default productView and categoryView queries already do. If you override the master component with your own query, carry the selection across or every <Metafield> on the page falls back to its default.

Resolution order

ConditionRendered
Declared with visibleToCustomer: falseNothing — not the default, not the placeholder.
Value is set on the entityThe value.
Value is unset and defaultValue was passeddefaultValue.
Value is unset and no defaultValueThe declaration's appearance.placeholder.
Neither existsNothing.
Stored type does not match the declared typeThe default, plus a development-mode console warning.
Hidden fields render nothing at all

A field declared visibleToCustomer: false is dropped from the customer-facing GraphQL response. If the component fell back to the default for it, a private field's placeholder text would be published on the storefront. So visibility is checked first, before the value and before any fallback.

Example: rendering a non-scalar value

The built-in renderer only handles strings and numbers — lists, groups, booleans and rich text need a render function. (Without one, nothing renders and a development-mode warning is logged.)

<Metafield
owner="product"
entity={product}
namespace="mytheme"
fieldKey="badges"
render={(value) =>
Array.isArray(value) ? (
<div className="product__badges">
{value.map((badge, i) => (
<span key={i} className={`badge badge--${badge.tone}`}>
{badge.label}
</span>
))}
</div>
) : null
}
/>

Example: using meta

The second argument to render describes the field:

KeyTypeDescription
typestringNormalized (lowercase) field type — declared type first, stored type otherwise.
isListbooleanPresent when the declaration marks the field as a list.
namestringThe declaration's display name, when declared.
placeholderstringThe declaration's appearance.placeholder, when declared.
isDefaultbooleantrue when what you are rendering is a fallback rather than stored data.
<Metafield
owner="product"
entity={product}
namespace="mytheme"
fieldKey="care"
render={(value, meta) => (
<p className={meta.isDefault ? 'text-muted-foreground italic' : undefined}>
{String(value)}
</p>
)}
/>

Page builder behaviour

Inside the page-builder iframe the component gains a violet "Live data" hover outline; clicking it opens an informational drawer describing the field, its definition status, and where its value is edited. A declared field that renders nothing on the storefront — hidden, or unset with no default — shows a dashed ghost chip instead, so it stays discoverable on the canvas.

None of this affects the storefront: the first render is always the bare production element, and the chrome mounts only after hydration inside the builder.

Metafield values are live entity data. The page builder never edits them.

Notes

  • The component is presentational only — no fetching, no entity-context fallback.
  • The declaration lookup is keyed owner.namespace.fieldKey. An undeclared field still renders whatever the GraphQL data contains, on a best-effort basis.
  • Development-mode console.warns cover the common misconfigurations: a declared field missing from the entity data (definition not provisioned yet, or the query does not select metafields), a stored/declared type mismatch, and a non-scalar value with no render prop. They are silenced in production builds.

For declaring metafields in theme.json, see Using Metafields in a Theme.



Support us


EverShop is an open-source project that relies on community support. If you find our project useful, please consider sponsoring us.