Skip to main content

๐Ÿ“ฆ FetchProductVariantsResult

TypeScript interface defining the return value structure of the getProductVariants() function.


๐Ÿ“‹ Interface Definitionโ€‹

interface FetchProductVariantsResult {
variants: Array<{
variant: ProductVariant;
product: Product;
}>;
error: string | null;
}

๐Ÿ” Propertiesโ€‹

variantsโ€‹

  • Type: Array<{ variant: ProductVariant; product: Product }>
  • Description: Array of variant-product pairs with requested fields and metafields
  • Always present: Yes (empty array if error or no results)

errorโ€‹

  • Type: string | null
  • Description: Error message if the fetch failed
  • Null when: Fetch was successful

ProductVariantโ€‹

interface ProductVariant {
id: string; // Always included
title: string; // Always included

// Additional fields (when requested via variantFields)
price?: {
amount: number;
currencyCode: string;
};
compareAtPrice?: {
amount: number;
currencyCode: string;
} | null;
availableForSale?: boolean;
selectedOptions?: Array<{
name: string;
value: string;
}>;
image?: Image | null;
sku?: string;
barcode?: string;
weight?: number;
weightUnit?: string;
requiresShipping?: boolean;
taxable?: boolean;
inventoryQuantity?: number;
inventoryPolicy?: string;

// Metafields (when requested)
metafields: Record<string, any>;
}

Productโ€‹

interface Product {
id: string; // Always included
title: string; // Always included
handle: string; // Always included

// Additional fields (when requested via productFields)
vendor?: string;
productType?: string;
tags?: string[];
descriptionHtml?: string;
description?: string;
images?: Image[];
featuredImage?: Image | null;
variants?: ProductVariant[];
seo?: {
title?: string;
description?: string;
};
createdAt?: string;
updatedAt?: string;

// Metafields (when requested)
metafields: Record<string, any>;
}

Imageโ€‹

interface Image {
id: string;
url: string;
altText: string | null;
width: number;
height: number;
}

๐Ÿงช Usage Examplesโ€‹

Basic Result Handlingโ€‹

const { variants, error } = await getProductVariants({
variantIds: [
"gid://shopify/ProductVariant/123456789",
"gid://shopify/ProductVariant/987654321",
],
});

if (error) {
console.error("Failed to fetch variants:", error);
return;
}

console.log(`Fetched ${variants.length} variants`);
variants.forEach(({ variant, product }) => {
console.log(`${variant.title} from ${product.title}`);
});

Accessing Additional Fieldsโ€‹

const result = await getProductVariants({
variantIds: [
"gid://shopify/ProductVariant/123456789",
"gid://shopify/ProductVariant/987654321",
],
productFields: ["vendor", "images"],
variantFields: ["price", "availableForSale", "selectedOptions"],
});

if (!result.error) {
result.variants.forEach(({ variant, product }) => {
// Access additional variant fields
console.log(`Price: $${variant.price?.amount}`);
console.log(`Available: ${variant.availableForSale}`);
console.log(`Options:`, variant.selectedOptions);

// Access additional product fields
console.log(`Vendor: ${product.vendor}`);
console.log(`Images: ${product.images?.length} images`);
});
}

Working with Metafieldsโ€‹

const result = await getProductVariants({
variantIds: [
"gid://shopify/ProductVariant/123456789",
"gid://shopify/ProductVariant/987654321",
],
productMetafields: [
{ field: "custom.brand", type: "single_line_text" },
{ field: "custom.warranty_years", type: "number_integer" },
],
variantMetafields: [
{ field: "custom.sku", type: "single_line_text" },
{ field: "custom.weight", type: "weight" },
],
options: {
camelizeKeys: true,
},
});

if (!result.error) {
result.variants.forEach(({ variant, product }) => {
// Access product metafields (camelized)
const brand = product.metafields.customBrand;
const warranty = product.metafields.customWarrantyYears;

// Access variant metafields (camelized)
const sku = variant.metafields.customSku;
const weight = variant.metafields.customWeight;

console.log(`${brand} ${product.title}`);
console.log(`SKU: ${sku}, Weight: ${weight?.value}${weight?.unit}`);
console.log(`Warranty: ${warranty} years`);
});
}

Bulk Cart Validationโ€‹

const cartItems = [
{ variantId: "gid://shopify/ProductVariant/123", quantity: 2 },
{ variantId: "gid://shopify/ProductVariant/456", quantity: 1 },
];

const result = await getProductVariants({
variantIds: cartItems.map((item) => item.variantId),
variantFields: ["price", "availableForSale"],
variantMetafields: [{ field: "custom.max_quantity", type: "number_integer" }],
});

if (!result.error) {
const validationResults = cartItems.map((cartItem, index) => {
const variantData = result.variants[index];

if (!variantData) {
return { valid: false, error: "Variant not found" };
}

const { variant } = variantData;
const maxQty = variant.metafields.customMaxQuantity || Infinity;

if (!variant.availableForSale) {
return { valid: false, error: "Not available for sale" };
}

if (cartItem.quantity > maxQty) {
return { valid: false, error: `Max quantity is ${maxQty}` };
}

return { valid: true, variant };
});

const allValid = validationResults.every((result) => result.valid);
console.log(`Cart validation: ${allValid ? "PASS" : "FAIL"}`);
}

Error Handling for Bulk Operationsโ€‹

const result = await getProductVariants({
variantIds: [
"gid://shopify/ProductVariant/valid1",
"gid://shopify/ProductVariant/invalid",
"gid://shopify/ProductVariant/valid2",
],
});

if (result.error) {
console.error("Bulk fetch failed:", result.error);
return;
}

// Check which variants were successfully fetched
const requestedIds = [
"gid://shopify/ProductVariant/valid1",
"gid://shopify/ProductVariant/invalid",
"gid://shopify/ProductVariant/valid2",
];

requestedIds.forEach((requestedId, index) => {
const found = result.variants.find(
({ variant }) => variant.id === requestedId
);

if (found) {
console.log(`โœ“ Found: ${found.variant.title}`);
} else {
console.log(`โœ— Missing: ${requestedId}`);
}
});

Building Product Comparisonโ€‹

interface ProductComparisonProps {
variantIds: string[];
}

async function ProductComparison({ variantIds }: ProductComparisonProps) {
const result = await getProductVariants({
variantIds,
productFields: ["vendor", "images", "tags"],
variantFields: ["price", "compareAtPrice", "selectedOptions"],
productMetafields: [
{ field: "custom.brand", type: "single_line_text" },
{ field: "custom.rating", type: "rating" },
],
});

if (result.error) {
return <div>Error loading comparison: {result.error}</div>;
}

return (
<div className="comparison-grid">
{result.variants.map(({ variant, product }) => (
<div key={variant.id} className="comparison-card">
{product.images?.[0] && (
<img
src={product.images[0].url}
alt={product.images[0].altText || product.title}
/>
)}
<h3>{product.title}</h3>
<p>{variant.title}</p>
<div className="price">
<span className="current">${variant.price?.amount}</span>
{variant.compareAtPrice && (
<span className="compare">${variant.compareAtPrice.amount}</span>
)}
</div>
<p>Brand: {product.metafields.customBrand}</p>
<p>Vendor: {product.vendor}</p>
<p>Rating: {product.metafields.customRating}/5</p>
<div className="options">
{variant.selectedOptions?.map(option => (
<span key={option.name}>
{option.name}: {option.value}
</span>
))}
</div>
</div>
))}
</div>
);
}

Type Guards for Bulk Resultsโ€‹

function isValidBulkResult(
result: FetchProductVariantsResult
): result is {
variants: Array<{ variant: ProductVariant; product: Product }>;
error: null;
} {
return result.error === null && result.variants.length > 0;
}

const result = await getProductVariants({
variantIds: ["gid://shopify/ProductVariant/123456789"],
});

if (isValidBulkResult(result)) {
// TypeScript knows variants array is populated and error is null
result.variants.forEach(({ variant, product }) => {
console.log(`${variant.title} from ${product.title}`);
});
}

Grouping by Productโ€‹

const result = await getProductVariants({
variantIds: [
"gid://shopify/ProductVariant/123", // Product A
"gid://shopify/ProductVariant/456", // Product A
"gid://shopify/ProductVariant/789", // Product B
],
});

if (!result.error) {
// Group variants by product
const groupedByProduct = result.variants.reduce(
(acc, { variant, product }) => {
if (!acc[product.id]) {
acc[product.id] = {
product,
variants: [],
};
}
acc[product.id].variants.push(variant);
return acc;
},
{} as Record<string, { product: Product; variants: ProductVariant[] }>
);

Object.values(groupedByProduct).forEach(({ product, variants }) => {
console.log(`Product: ${product.title}`);
console.log(`Variants: ${variants.map((v) => v.title).join(", ")}`);
});
}

๐Ÿšจ Error Statesโ€‹

Common error messages you might encounter:

  • "One or more variants not found" - Some variant IDs don't exist
  • "Access denied" - Insufficient permissions for the access token
  • "Invalid variant ID format" - One or more malformed variant IDs
  • "Too many variants requested" - Batch size exceeds limits
  • Network-related errors from the underlying fetch operation

๐Ÿ”„ Partial Success Handlingโ€‹

Unlike single variant fetches, bulk operations may have partial success:

const result = await getProductVariants({
variantIds: [
"gid://shopify/ProductVariant/valid1",
"gid://shopify/ProductVariant/invalid",
"gid://shopify/ProductVariant/valid2",
],
});

// Even if some variants fail, others may succeed
if (!result.error && result.variants.length > 0) {
console.log(
`Successfully fetched ${result.variants.length} out of 3 variants`
);

// Process the successful ones
result.variants.forEach(({ variant, product }) => {
console.log(`โœ“ ${variant.title} from ${product.title}`);
});
}

โœ… Back to: Options Types โ†’