⚙️ getProduct() Options
The getProduct() function accepts a single argument of type GetProductOptions. It lets you control how the product is fetched, what extra data is included, and how that data is shaped.
🧾 Input Shape
interface GetProductOptions {
id?: string;
handle?: string;
customMetafields?: CustomMetafieldDefinition[];
variantMetafields?: CustomMetafieldDefinition[];
options: {
locale?: string;
resolveFiles?: boolean;
renderRichTextAsHtml?: boolean;
camelizeKeys?: boolean;
transformMetafields?: (raw, casted, definitions) => Record<string, any>;
transformVariantMetafields?: (raw, casted, definitions) => Record<string, any>;
};
}
🔑 id or handle
id?: string
handle?: string
- Use one or the other to identify the product to fetch.
handleis recommended for URL-based routing.- If neither is provided, the function will return an error.
🧩 customMetafields
customMetafields?: CustomMetafieldDefinition[]
Use this to fetch product-level metafields, casted and typed for use in your frontend.
Each metafield definition looks like:
{ field: "custom.warranty", type: "single_line_text" }
You can define multiple metafields here and retrieve them using the metafields key on the returned product.
➡️ See Custom Metafields →
🔀 variantMetafields
variantMetafields?: CustomMetafieldDefinition[]
Similar to customMetafields, but applied to each product variant.
This is perfect for cases like:
- Variant-specific sizing, weight, or specs
- Conditional rendering of swatches or details
Returned in each variant object under metafields.
⚙️ options
The options object lets you customize how the returned data is parsed and transformed.
📍 locale
locale?: string
Shopify's storefront API supports localization — pass the desired locale like "fr" or "en" to retrieve translated content (if available).
🖼️ resolveFiles
resolveFiles?: boolean // default: true
When true, file metafields will be resolved and returned as structured objects. When false, you'll get raw GraphQL strings instead.
Useful for:
- PDFs
- Certificates
- Image metafields
📝 renderRichTextAsHtml
renderRichTextAsHtml?: boolean // default: false
Enables parsing of rich_text metafields into raw HTML so they can be injected directly into your frontend using:
<div dangerouslySetInnerHTML={{ __html: data.metafields.custom.description }} />
🐫 camelizeKeys
camelizeKeys?: boolean // default: true
Converts all metafield keys to camelCase.
For example, custom.product_warranty becomes custom.productWarranty.
This is highly recommended for JavaScript projects to improve DX and consistency.
🔧 transformMetafields
transformMetafields?: (
raw: Record<string, Record<string, string>>,
casted: Record<string, any>,
definitions: ResolvedMetafieldInfo[]
) => Record<string, any>
Allows you to intercept and modify product-level metafields after casting.
Use this for:
- Grouping or flattening fields
- Creating computed summaries
- Normalizing missing values
Example:
transformMetafields: (raw, casted) => {
const summary = [
casted["custom.brand"] && `Brand: ${casted["custom.brand"]}`,
casted["custom.weight"] && `Weight: ${casted["custom.weight"]} kg`,
casted["custom.warranty"] && `Warranty: ${casted["custom.warranty"]}`,
]
.filter(Boolean)
.join(" · ");
return {
...casted,
metaSummary: summary || "No extra details available",
isHeavy: casted["custom.weight"] >= 30,
brandSlug: casted["custom.brand"]
? casted["custom.brand"].toLowerCase().replace(/\s+/g, "-")
: null,
};
}
This is especially useful for referenced metafields such as
Product,Page,File, orMetaobject. These fields often return nested objects or raw handles that aren’t immediately usable. WithtransformMetafields, you can resolve them (whenresolveFilesis enabled), flatten what you need, and expose only the useful parts to your frontend — like a product’s title and image, a file URL, or a metaobject’s custom layout. It bridges the gap between Shopify’s complex data and the clean, minimal data you actually want to render.
🔧 transformVariantMetafields
transformVariantMetafields?: (
raw: Record<string, Record<string, string>>,
casted: Record<string, any>,
definitions: ResolvedMetafieldInfo[]
) => Record<string, any>
Lets you modify variant-level metafields, much like transformMetafields.
This is ideal when you want to:
- Flatten variant metafields into readable fields
- Create flags like
isPreOrderorisHeavyVariant - Enrich reference data (like bundled products)
🧠 Simple Example
transformVariantMetafields: (raw, casted) => {
return {
...casted,
isPremium: casted["custom.material"] === "Premium Steel",
};
}
You can now access this directly in your frontend:
{variant.metafields.isPremium && <span className="badge">Premium</span>}
🧪 Advanced Example — Bundle Enrichment
Below is a more advanced use case where you enrich a Product reference stored in a variant metafield (e.g. simple_bundles.bundled_variants) by resolving its featured image.
transformVariantMetafields: async (raw, casted, defs) => {
const flattened: Record<string, any> = { ...casted };
const bundledProducts = flattened?.simple_bundles?.bundled_variants;
if (Array.isArray(bundledProducts)) {
flattened.simple_bundles.bundled_variants = [...bundledProducts];
for (let i = 0; i < bundledProducts.length; i++) {
const product = bundledProducts[i];
const handle =
typeof product === "string" ? product : product?.handle;
if (!handle) continue;
try {
const result = await getProduct({
handle,
customMetafields: [],
options: {
renderRichTextAsHtml: true,
resolveFiles: true,
camelizeKeys: true,
},
});
if (result.error) {
throw new Error(result.error);
}
if (result.data?.featuredImage) {
flattened.simple_bundles.bundled_variants[i] = {
...(typeof product === "string"
? { handle: product }
: product),
featuredImage: {
originalSrc: result.data.featuredImage.originalSrc,
altText: result.data.featuredImage.altText ?? null,
},
};
}
} catch (error) {
console.warn(`⚠️ Failed to fetch bundled product ${handle}:`, error);
}
}
}
return flattened;
}
In this example, the variant metafield
simple_bundles.bundled_variantscontains referenced Shopify products. However, the Shopify API response does not include key data — like the product’s image.This logic re-fetches each referenced product using
getProduct()and injects the missing image data (likefeaturedImage.originalSrc) into the final metafield structure.This lets you display complete bundle items — with images and alt text — on the frontend, even if Shopify's API doesn't provide it out of the box.
✅ Summary
getProduct() is fully customizable — you control:
- How the product is fetched (
idorhandle) - What metafields to retrieve
- How to transform and present the data
Next: Custom Metafields →
Description
Learn about all available options for getProduct, including metafield support, locale, and data transformation utilities.