examples/functions/product-mapping/README.md
E-commerce teams need to organize products into logical groups and manage color variants, but manually creating product maps and color variants for each product based on Shopify tags is time-consuming and error-prone. This creates inconsistencies in product organization and variant management. Many commerence teams already have access to this automation either via csv uploads or via configuration in the warehouse.
This Sanity Function automatically processes Shopify product tags to create product maps and color variants when products are created or updated. When a product has tags starting with sanity-parent- or sanity-color-, the function automatically creates or updates the corresponding productMap and colorVariant documents, then links them back to the product.
This function is built to be compatible with the Sanity E-commerce Shopify template.. It works specifically with Shopify product data and requires the product, productMap, and colorVariant document types. Install Sanity's Shopify template: npm create sanity@latest -- --template shopify
This function works directly with Sanity Connect for Shopify to automatically process product data as it syncs from your Shopify store. When Shopify products are created or updated through Sanity Connect, this function:
productMap documents from tags starting with sanity-parent-colorVariant documents from tags starting with sanity-color-sanity-parent-summer-collection → Creates/updates productMap: "summer-collection"sanity-color-blue → Creates/updates colorVariant: "blue"sanity-parent-winter-jackets → Creates/updates productMap: "winter-jackets"The function ensures that your Sanity dataset stays organized and up-to-date with your Shopify product catalog automatically.
If you're using the Sanity E-commerce template, you'll need to add the productMap and colorVariant schema types:
schemaTypes/productMap.ts:import {defineField, defineType} from 'sanity'
export const productMapType = defineType({
name: 'productMap',
title: 'Product Map',
type: 'document',
fields: [
defineField({
name: 'id',
title: 'ID',
type: 'string',
validation: (Rule: any) => Rule.required(),
}),
defineField({
name: 'products',
title: 'Products',
type: 'array',
of: [
{
type: 'reference',
to: [{type: 'product'}],
},
],
}),
defineField({
name: 'description',
title: 'Description',
type: 'text',
rows: 3,
}),
defineField({
name: 'careInstructions',
title: 'Care Instructions',
type: 'array',
of: [{type: 'string'}],
description: 'Add care instructions for the products',
}),
],
})
schemaTypes/colorVariant.ts:import {defineField, defineType} from 'sanity'
export const colorVariantType = defineType({
name: 'colorVariant',
title: 'Color Variant',
type: 'document',
fields: [
defineField({
name: 'colorName',
title: 'Color Name',
type: 'string',
validation: (Rule: any) => Rule.required(),
}),
defineField({
name: 'colorValue',
title: 'Color',
type: 'color',
description: 'Pick a solid color',
}),
defineField({
name: 'pattern',
title: 'Pattern Image',
type: 'image',
description: 'Upload a pattern image instead of selecting a solid color',
options: {
hotspot: true,
},
}),
],
preview: {
select: {
title: 'colorName',
media: 'pattern',
color: 'colorValue',
},
prepare({title, media, color}: {title: any; media: any; color: any}) {
return {
title,
media:
media ||
(color?.hex
? {
_type: 'color',
hex: color.hex,
}
: null),
}
},
},
})
sanity.config.ts:import {productMapType} from './schemaTypes/productMap'
import {colorVariantType} from './schemaTypes/colorVariant'
export default defineConfig({
// ... other config
schema: {
types: [
// ... your existing types
productMapType,
colorVariantType,
],
},
})
product schema. Add these fields to your product schema:defineField({
name: 'productMap',
title: 'Product Map',
type: 'reference',
to: [{type: 'productMap'}],
description: 'Automatically populated by the product-mapping function',
}),
defineField({
name: 'colorVariant',
title: 'Color Variant',
type: 'reference',
to: [{type: 'colorVariant'}],
description: 'Automatically populated by the product-mapping function',
}),
Important: Run these commands from the root of your project (not inside the studio/ folder).
Initialize the example
Run this if you haven't initialized blueprints:
npx sanity blueprints init
You'll be prompted to select your organization and Sanity studio.
Then run:
npx sanity blueprints add function --example product-mapping
Add configuration to your blueprint
// sanity.blueprint.ts
import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'
export default defineBlueprint({
resources: [
defineDocumentFunction({
name: 'product-mapping',
memory: 1,
timeout: 10,
src: './functions/product-mapping',
event: {
on: ['create', 'update'],
filter:
"_type == 'product' && (delta::changedAny(store.tags) || (delta::operation() == 'create' && defined(store.tags)))",
projection:
'{_id, _type, store, colorVariant, productMap, "operation": delta::operation()}',
},
}),
],
})
Install dependencies
Install dependencies in the project root:
npm install
And install function dependencies:
npm install @sanity/functions
cd functions/product-mapping
npm install
cd ../..
Add the required schema types
Follow the instructions in the "Compatible Templates" section above to add the productMap and colorVariant schema types to your project.
Deploy your schema
From the studio folder, deploy your updated schema:
# From the studio/ folder (adjust path as needed for template structure)
cd studio
npx sanity schema deploy
cd ..
You can test the product-mapping function locally using the Sanity CLI before deploying it to production.
Important: Document functions require that the document ID used in testing actually exists in your dataset. The examples below show how to work with real document IDs.
The best way to test this function is with actual products from your Shopify store that have been synced through Sanity Connect:
In your Shopify admin, create or edit products and add tags with the required prefixes:
sanity-parent-summer-collection
sanity-color-blue
sanity-parent-winter-jackets
sanity-color-red
After adding the tags in Shopify:
Check your Sanity Studio to confirm:
productMap documents were created (e.g., "summer-collection", "winter-jackets")colorVariant documents were created (e.g., "blue", "red")To test the update functionality:
sanity-parent- or sanity-color- tags)If you prefer to test using the Sanity CLI instead of Shopify Connect, you can use the following methods:
Since document functions require the document ID to exist in your dataset, create a test product first:
# From the studio/ folder, create a test product with tags
cd studio
cat > test-product.json << EOF
{
"_type": "product",
"title": "Test Product with Tags",
"store": {
"tags": ["sanity-parent-summer-collection", "sanity-color-blue", "cotton", "t-shirt"],
"slug": {
"current": "test-product-tags"
}
}
}
EOF
npx sanity documents create test-product.json --replace
# Back to project root for function testing
cd ..
npx sanity functions test product-mapping --file studio/test-product.json --dataset production --with-user-token
Alternative: Test with a real product from your dataset:
# From the studio/ folder, find and export an existing product
cd studio
npx sanity documents query "*[_type == 'product'][0]" > ../real-product.json
# Back to project root for function testing
cd ..
npx sanity functions test product-mapping --file real-product.json --dataset production --with-user-token
Start the development server for interactive testing:
npx sanity functions dev
This opens an interactive playground where you can test functions with custom data.
For custom data testing, you still need to use a real document ID that exists in your dataset:
# From the studio/ folder, create or find a real document ID
cd studio
REAL_DOC_ID=$(npx sanity documents query "*[_type == 'product'][0]._id" | tr -d '"')
# Create a temporary JSON file with custom data in project root
cd ..
cat > test-custom-product.json << EOF
{
"_type": "product",
"_id": "$REAL_DOC_ID",
"title": "Custom Test Product",
"store": {
"tags": ["sanity-parent-winter-collection", "sanity-color-red", "wool", "sweater"],
"slug": {
"current": "custom-test-product"
}
}
}
EOF
# Test with the custom data file
npx sanity functions test product-mapping --file test-custom-product.json --dataset production --with-user-token
The most reliable approach is to test with existing products from your dataset:
# From the studio/ folder, find and export a product that has tags
cd studio
npx sanity documents query "*[_type == 'product' && defined(store.tags)][0]" > ../test-real-product.json
# Back to project root for function testing
cd ..
npx sanity functions test product-mapping --file test-real-product.json --dataset production --with-user-token
The function includes comprehensive logging. Check the output for:
// Function logs include:
console.log('👋 Your Sanity Function was called at', new Date().toISOString())
console.log('🏷️ Processing tags for product:', _id, 'Tags:', tags)
console.log('✅ Created productMap:', productMapName, 'with ID:', newProductMap._id)
console.log('✅ Created colorVariant:', colorName, 'with ID:', newColorVariant._id)
sanity-parent- or sanity-color-npx sanity documents query to find suitable test documentsnpx sanity functions logs product-mappingproduct document type with store.tags field (existing)productMap document type (add using schema above)colorVariant document type (add using schema above)product schema with productMap and colorVariant reference fieldssanity-parent-{name} for product mapssanity-color-{name} for color variantsWhen a product is created or updated with specific Shopify tags, the function automatically:
store.tags fieldsanity-parent- tags to create or update product mapssanity-color- tags to create color variantsSample input document:
{
"_type": "product",
"_id": "product-123",
"title": "Summer Cotton T-Shirt",
"store": {
"tags": ["sanity-parent-summer-collection", "sanity-color-blue", "cotton", "t-shirt", "casual"],
"slug": {
"current": "summer-cotton-t-shirt"
}
}
}
Result:
productMap document with ID productMap-summer-collection is created/updatedcolorVariant document with ID colorVariant-blue is createdUpdate the tag prefixes by modifying the filter conditions:
// Change the parent tag prefix
const parentTags = tags.filter((tag) => tag.startsWith('custom-parent-'))
const productMapName = tag.replace('custom-parent-', '')
// Change the color tag prefix
const colorTags = tags.filter((tag) => tag.startsWith('custom-color-'))
const colorName = tag.replace('custom-color-', '')
Extend the function to create additional document types based on other tag patterns:
// Process category tags
const categoryTags = tags.filter((tag) => tag.startsWith('sanity-category-'))
for (const tag of categoryTags) {
const categoryName = tag.replace('sanity-category-', '')
// Create category documents...
}
Customize the created document structure:
const newProductMap = await client.create({
_id: productMapId,
_type: 'productMap',
id: productMapName,
products: [{_key: `product-${_id}`, _ref: _id, _type: 'reference'}],
description: `Product map for ${productMapName}`,
careInstructions: [],
// Add custom fields
category: 'auto-generated',
createdAt: new Date().toISOString(),
})
Modify tag processing to handle different formats:
// Handle comma-separated tags in a single string
if (typeof store.tags === 'string') {
tags = store.tags
.split(',')
.map((tag) => tag.trim())
.filter((tag) => tag.length > 0)
}
// Handle pipe-separated tags
if (typeof store.tags === 'string') {
tags = store.tags
.split('|')
.map((tag) => tag.trim())
.filter((tag) => tag.length > 0)
}
Error: "No tags found for product"
store.tags fieldError: "Tags format not recognized"
Error: "Failed to create productMap/colorVariant"
Function processes products but no documents are created
sanity-parent- or sanity-color-Duplicate products in productMap