Skip to main content

🎯 getProductVariants() Overview

getProductVariants() fetches multiple product variants by their IDs, including complete product context, variant-specific data, and custom metafields. This is a PRO tier only function.


✅ When to Use This

  • Bundle creation - Building complex product bundles with multiple variants
  • Cart operations - Bulk operations on multiple cart items
  • Mounting products - Fetching all variants for product mounting/configuration
  • Inventory management - Bulk checking variant availability and details
  • Comparison features - Comparing multiple variants side-by-side

🧩 Used with @nextshopkit/pro

Available only in PRO tier:

lib/nextshopkit/client.ts
import {
createShopifyClient,
GetProductVariantsOptions,
FetchProductVariantsResult,
} from "@nextshopkit/pro";

const client = createShopifyClient({
shop: process.env.SHOPIFY_STORE_DOMAIN!,
token: process.env.SHOPIFY_ACCESS_TOKEN!,
apiVersion: "2025-04",
enableMemoryCache: true,
defaultCacheTtl: 300,
enableVercelCache: true,
defaultRevalidate: 60,
});

export const getProductVariants = async (
args: GetProductVariantsOptions
): Promise<FetchProductVariantsResult> => client.getProductVariants(args);

export default client;

🚀 Basic Usage

import { getProductVariants } from "lib/nextshopkit/client";

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

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

console.log(`Fetched ${data.variants.length} variants`);
data.variants.forEach((item) => {
console.log(`${item.variant.title} - $${item.variant.price.amount}`);
console.log(`Product: ${item.product.title}`);
});

🎯 Advanced Usage with Metafields

// Fetch multiple variants with custom 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" },
{ field: "custom.category", type: "single_line_text" },
],
variantMetafields: [
{ field: "custom.power_output", type: "number_integer" },
{ field: "custom.dimensions", type: "single_line_text" },
{ field: "custom.weight", type: "weight" },
{ field: "custom.color_code", type: "color" },
],
options: {
camelizeKeys: true,
resolveFiles: true,
renderRichTextAsHtml: true,
},
});

📦 Real-World Implementation

Here's a complete function for mounting products fetching:

lib/nextshopkit/mounting.ts
export const fetchMountingProducts = async (variantIds: string[]) => {
try {
const { data, error } = await getProductVariants({
variantIds,
productMetafields: [
{ field: "custom.mounting_compatible", type: "boolean" },
{ field: "custom.mounting_category", type: "single_line_text" },
{ field: "custom.installation_guide", type: "rich_text" },
{ field: "custom.required_tools", type: "list.single_line_text" },
],
variantMetafields: [
{ field: "custom.mounting_type", type: "single_line_text" },
{ field: "custom.weight_capacity", type: "weight" },
{ field: "custom.dimensions", type: "single_line_text" },
{ field: "custom.material", type: "single_line_text" },
],
options: {
camelizeKeys: true,
resolveFiles: true,
renderRichTextAsHtml: true,
},
});

if (error || !data) {
throw new Error(`Failed to fetch mounting products: ${error}`);
}

// Process and categorize mounting products
const mountingProducts = data.variants
.filter((item) => item.product.metafields.customMountingCompatible)
.map((item) => ({
variant: {
id: item.variant.id,
title: item.variant.title,
price: item.variant.price,
availableForSale: item.variant.availableForSale,
mountingType: item.variant.metafields.customMountingType,
weightCapacity: item.variant.metafields.customWeightCapacity,
dimensions: item.variant.metafields.customDimensions,
material: item.variant.metafields.customMaterial,
},
product: {
id: item.product.id,
title: item.product.title,
handle: item.product.handle,
mountingCategory: item.product.metafields.customMountingCategory,
installationGuide: item.product.metafields.customInstallationGuide,
requiredTools: item.product.metafields.customRequiredTools,
},
}));

// Group by mounting category
const categorized = mountingProducts.reduce(
(acc, product) => {
const category = product.product.mountingCategory || "general";
if (!acc[category]) {
acc[category] = [];
}
acc[category].push(product);
return acc;
},
{} as Record<string, typeof mountingProducts>
);

return {
products: mountingProducts,
categorized,
totalCount: mountingProducts.length,
categories: Object.keys(categorized),
};
} catch (error) {
console.error("Mounting products fetch error:", error);
return {
products: [],
categorized: {},
totalCount: 0,
categories: [],
error: error.message,
};
}
};

🛒 Cart Integration Example

lib/nextshopkit/cart-utils.ts
export const validateCartItems = async (
cartItems: Array<{ variantId: string; quantity: number }>
) => {
try {
const variantIds = cartItems.map((item) => item.variantId);

const { data, error } = await getProductVariants({
variantIds,
variantMetafields: [
{ field: "custom.max_quantity", type: "number_integer" },
{ field: "custom.min_quantity", type: "number_integer" },
{ field: "custom.stock_level", type: "number_integer" },
],
options: {
camelizeKeys: true,
},
});

if (error || !data) {
throw new Error(`Failed to validate cart items: ${error}`);
}

const validationResults = cartItems.map((cartItem) => {
const variantData = data.variants.find(
(item) => item.variant.id === cartItem.variantId
);

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

const { variant } = variantData;
const maxQty = variant.metafields.customMaxQuantity || Infinity;
const minQty = variant.metafields.customMinQuantity || 1;
const stockLevel = variant.metafields.customStockLevel;

// Validation checks
if (!variant.availableForSale) {
return {
variantId: cartItem.variantId,
valid: false,
error: "Product is not available for sale",
};
}

if (cartItem.quantity < minQty) {
return {
variantId: cartItem.variantId,
valid: false,
error: `Minimum quantity is ${minQty}`,
};
}

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

if (stockLevel && cartItem.quantity > stockLevel) {
return {
variantId: cartItem.variantId,
valid: false,
error: `Only ${stockLevel} items in stock`,
};
}

return {
variantId: cartItem.variantId,
valid: true,
variant: variant,
};
});

return {
allValid: validationResults.every((result) => result.valid),
results: validationResults,
invalidItems: validationResults.filter((result) => !result.valid),
};
} catch (error) {
console.error("Cart validation error:", error);
return {
allValid: false,
results: [],
invalidItems: [],
error: error.message,
};
}
};

🔍 Key Features

  • Bulk fetching: Get multiple variants in a single request
  • Complete context: Each variant includes its full product data
  • Metafield support: Include custom metafields for both products and variants
  • Transform functions: Reshape metafield data for your UI
  • Type safety: Full TypeScript support with proper interfaces
  • Efficient: Optimized for bulk operations and reduced API calls

📊 Return Structure

{
variants: Array<{
variant: ProductVariant; // The specific variant data
product: Product; // Complete product context
}>;
error: string | null; // Error message if any
}

Each item in the variants array contains:

  • variant: Complete variant data with metafields
  • product: Full product context for that variant

🚨 PRO Tier Only

PRO Tier Required

getProductVariants() is only available in @nextshopkit/pro. It's not included in the CORE tier.

To upgrade to PRO tier, visit NextShopKit Pro.


✅ Next: Options Reference → | Types Reference →