Shopify Integration
Garmint integrates with Shopify to fetch garment blanks and product data for mockup generation.
Overview
Storefront API Read-only access to products and variants
Admin API Full product management (optional)
Configuration
Environment Variables
# Storefront API (read-only, safe for client)
NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN=yourstore.myshopify.com
NEXT_PUBLIC_SHOPIFY_STOREFRONT_TOKEN=shpat_xxxxxxxxxxxxx
# Admin API (server-only, optional)
SHOPIFY_ADMIN_TOKEN=shpat_xxxxxxxxxxxxx
# API versions (optional)
SHOPIFY_STOREFRONT_API_VERSION=2024-07
SHOPIFY_ADMIN_API_VERSION=2024-07
Shopify Setup
Go to your Shopify admin
Settings → Apps and sales channels → Develop apps
Create a new app
Configure Storefront API scopes:
unauthenticated_read_product_listings
unauthenticated_read_product_inventory
Install the app and copy tokens
Usage
Check Configuration
import { isShopifyConfigured } from "@/lib/shopify" ;
if ( isShopifyConfigured ()) {
// Use Shopify data
const products = await fetchProducts ();
} else {
// Fall back to mock data
const products = getMockGarments ();
}
Storefront Query
import { storefrontQuery } from "@/lib/shopify" ;
interface ProductsResponse {
products : {
edges : Array <{ node : ProductNode }>;
};
}
const data = await storefrontQuery < ProductsResponse >( `
query Products($first: Int!) {
products(first: $first) {
edges {
node {
id
title
description
tags
featuredImage { url }
variants(first: 20) {
edges {
node {
id
price { amount currencyCode }
availableForSale
selectedOptions { name value }
}
}
}
}
}
}
}
` , { first: 50 });
Admin Request (Server Only)
import { adminRequest } from "@/lib/shopify" ;
// Get product by ID
const product = await adminRequest <{ product : AdminProduct }>(
`products/ ${ productId } .json`
);
// Update product
await adminRequest ( `products/ ${ productId } .json` , {
method: "PUT" ,
body: JSON . stringify ({
product: { title: "New Title" }
}),
});
Garment Type
Shopify products are transformed to the Garment type:
interface Garment {
id : string ;
title : string ;
handle : string ;
category : string ;
color : string ;
fabric ?: string ;
previewUrl : string ;
variants : GarmentVariant [];
}
interface GarmentVariant {
id : string ;
size : string ;
color ?: string ;
price : number ;
available : boolean ;
}
function toGarment ( node : StorefrontProductNode ) : Garment {
return {
id: node . id ,
title: node . title ,
handle: node . handle ,
category: getCategoryFromTags ( node . tags ),
color: getColorFromOptions ( node . variants . edges [ 0 ]?. node . selectedOptions ),
fabric: getFabricFromMetafield ( node . metafields ),
previewUrl: node . featuredImage ?. url ?? PLACEHOLDER_IMAGE ,
variants: node . variants . edges . map (({ node : v }) => ({
id: v . id ,
size: getSizeFromOptions ( v . selectedOptions ),
price: parseFloat ( v . price . amount ),
available: v . availableForSale ,
})),
};
}
Product Blocklist
Exclude specific products from display:
// lib/productBlocklist.ts
export const blockedHandles = new Set ([
"test-product" ,
"sample-item" ,
"do-not-show" ,
]);
export function isBlocked ( handle : string ) : boolean {
return blockedHandles . has ( handle . toLowerCase ());
}
Usage:
const products = await fetchProducts ();
const filtered = products . filter ( p => ! isBlocked ( p . handle ));
Mock Data Fallback
Without Shopify credentials, use mock data:
// lib/mockGarments.ts
export const mockGarments : Garment [] = [
{
id: "mock-black-tee" ,
title: "Classic Heavyweight Tee" ,
handle: "classic-heavyweight-tee" ,
category: "T-Shirts" ,
color: "Black" ,
fabric: "6.1 oz 100% Cotton" ,
previewUrl: "/samples/black-tee.png" ,
variants: [
{ id: "v1" , size: "S" , price: 24.00 , available: true },
{ id: "v2" , size: "M" , price: 24.00 , available: true },
{ id: "v3" , size: "L" , price: 24.00 , available: true },
{ id: "v4" , size: "XL" , price: 26.00 , available: true },
],
},
// More products...
];
Hooks
useShopifyGarments
// hooks/useShopifyGarments.ts
export function useShopifyGarments () {
const [ garments , setGarments ] = useState < Garment []>([]);
const [ loading , setLoading ] = useState ( true );
useEffect (() => {
async function load () {
try {
const res = await fetch ( "/api/products" );
const data = await res . json ();
setGarments ( data . products );
} catch ( error ) {
console . error ( "Failed to load garments:" , error );
setGarments ( getMockGarments ());
} finally {
setLoading ( false );
}
}
load ();
}, []);
return { garments , loading };
}
API Route
GET /api/products
Fetches products from Shopify or returns mocks:
// app/api/products/route.ts
export async function GET () {
try {
if ( isShopifyConfigured ()) {
const products = await fetchShopifyProducts ();
return Response . json ({ products });
}
return Response . json ({
products: getMockGarments (),
source: "mock" ,
});
} catch ( error ) {
return Response . json ({
error: "Failed to fetch products"
}, { status: 500 });
}
}
GraphQL Queries
Fetch All Products
query Products ( $first : Int ! , $query : String ) {
products ( first : $first , query : $query ) {
edges {
node {
id
title
handle
description
tags
productType
createdAt
featuredImage {
url
altText
}
options {
name
values
}
variants ( first : 50 ) {
edges {
node {
id
title
availableForSale
quantityAvailable
price {
amount
currencyCode
}
selectedOptions {
name
value
}
image {
url
}
}
}
}
metafields ( identifiers : [
{ namespace : "custom" , key : "fabric" },
{ namespace : "custom" , key : "weight" }
]) {
key
value
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
Fetch Single Product
query Product ( $handle : String ! ) {
product ( handle : $handle ) {
id
title
# ... same fields
}
}
Error Handling
try {
const data = await storefrontQuery ( query , variables );
return data ;
} catch ( error ) {
if ( error . message . includes ( "not configured" )) {
// Fall back to mocks
return { products: getMockGarments () };
}
console . error ( "Shopify query failed:" , error );
throw error ;
}
Caching
Storefront queries are cached for 30 seconds:
const response = await fetch ( url , {
// ...
next: { revalidate: 30 },
});
For real-time inventory, reduce or disable caching:
const response = await fetch ( url , {
// ...
cache: 'no-store' ,
});
Best Practices
Use the Storefront API for client-facing product data. It’s read-only and safe to expose.
Never expose Admin API tokens to the client. Use them only in server-side code.
Test with mock data first to ensure your UI works, then connect Shopify for real products.