examples/functions/stale-products-analysis/README.md
E-commerce teams need to monitor the freshness of products featured on their pages to ensure they're showcasing current and relevant items. Manually tracking product ages and identifying stale content across page modules is time-consuming and often overlooked, leading to outdated product displays that can negatively impact user experience and sales.
This Sanity Function automatically analyzes the age of products referenced in page modules whenever a page is updated. It calculates how long products have been in the system, identifies stale products (older than 30 days), and stores this analysis data directly on the page document for easy monitoring and reporting.
This function is built to be compatible with the Sanity E-commerce Shopify template. It works specifically well with page documents that have modules containing product references in a grid layout structure. Install this template with: npm create sanity@latest -- --template "shopify"
You'll need to add a productAgeAnalysis field to your page document schema:
defineField({
name: 'modules',
type: 'array',
description: 'Editorial modules to associate with this collection',
of: [
defineArrayMember({type: 'grid'}),
// Additional types can be added
],
group: 'editorial',
}),
defineField({
name: 'productAgeAnalysis',
title: 'Product Age Analysis',
type: 'array',
of: [
{
type: 'object',
fields: [
{
name: 'product',
title: 'Product',
type: 'reference',
to: [{type: 'product'}],
},
{
name: 'ageInDays',
title: 'Age in Days',
type: 'number',
},
{
name: 'updatedAgeInDays',
title: 'Days Since Last Update',
type: 'number',
},
{
name: 'isOld',
title: 'Is Stale Product',
type: 'boolean',
},
{
name: 'createdAt',
title: 'Created At',
type: 'datetime',
},
{
name: 'lastUpdated',
title: 'Last Updated',
type: 'datetime',
},
],
preview: {
select: {
title: 'product.title',
productTitle: 'product.store.title',
ageInDays: 'ageInDays',
updatedAgeInDays: 'updatedAgeInDays',
isOld: 'isOld',
createdAt: 'createdAt',
lastUpdated: 'lastUpdated',
},
prepare({title, productTitle, ageInDays, updatedAgeInDays, isOld}) {
const displayTitle = productTitle || title || 'Untitled Product'
const createdAgeText = ageInDays ? `${ageInDays}d old` : 'Unknown age'
const updatedAgeText = updatedAgeInDays
? `${updatedAgeInDays}d since update`
: 'Unknown'
const status = isOld ? '🔴 OLD' : '🟢 FRESH'
return {
title: displayTitle,
subtitle: `${status} - Created: ${createdAgeText} | Updated: ${updatedAgeText}`,
}
},
},
},
],
description: 'Automatically populated by the stale-products-analysis function',
readOnly: true,
}),
Important: Run these commands from the root of your project (not inside the studio/ folder).
Initialize the example
For a new project:
npx sanity blueprints init --example stale-products-analysis
For an existing project:
npx sanity blueprints add function --example stale-products-analysis
You'll be prompted to select your organization and Sanity studio.
Add configuration to your blueprint
// sanity.blueprint.ts
import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'
export default defineBlueprint({
resources: [
defineDocumentFunction({
name: 'stale-products-analysis',
memory: 1,
timeout: 30,
src: './functions/stale-products-analysis',
event: {
on: ['create', 'update'],
filter:
"_type == 'page' && (delta::changedAny(modules) || (delta::operation() == 'create' && defined(modules)))",
projection: '{_id, _type, modules}',
},
}),
],
})
Install dependencies
Install dependencies in the project root:
npm install
And install function dependencies:
npm install @sanity/functions
cd functions/stale-products-analysis
npm install
cd ../..
Add the required schema field
Follow the instructions in the "Compatible Templates" section above to add the productAgeAnalysis field to your page document schema.
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 stale-products-analysis 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.
Since document functions require the document ID to exist in your dataset, create a test page first:
# From the studio/ folder, create a test page with product references
cd studio
cat > test-page.json << EOF
{
"_type": "page",
"title": "Test Page with Products",
"modules": [
{
"_type": "grid",
"_key": "test-grid",
"items": [
{
"_type": "productReference",
"_key": "test-product-ref",
"productWithVariant": {
"product": {
"_ref": "existing-product-id",
"_type": "reference"
}
}
}
]
}
]
}
EOF
npx sanity documents create test-page.json --replace
# Back to project root for function testing
cd ..
npx sanity functions test stale-products-analysis --file studio/test-page.json --dataset production --with-user-token
Alternative: Test with a real page from your dataset:
# From the studio/ folder, find and export an existing page
cd studio
npx sanity documents query "*[_type == 'page'][0]" > ../real-page.json
# Back to project root for function testing
cd ..
npx sanity functions test stale-products-analysis --file real-page.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 == 'page'][0]._id" | tr -d '"')
# Create a temporary JSON file with custom data in project root
cd ..
cat > test-custom-page.json << EOF
{
"_type": "page",
"_id": "$REAL_DOC_ID",
"title": "Custom Test Page",
"modules": [
{
"_type": "grid",
"_key": "custom-grid",
"items": [
{
"_type": "productReference",
"_key": "custom-product-ref",
"productWithVariant": {
"product": {
"_ref": "existing-product-id",
"_type": "reference"
}
}
}
]
}
]
}
EOF
# Test with the custom data file
npx sanity functions test stale-products-analysis --file test-custom-page.json --dataset production --with-user-token
The most reliable approach is to test with existing pages from your dataset:
# From the studio/ folder, find and export a page that has product modules
cd studio
npx sanity documents query "*[_type == 'page' && defined(modules)][0]" > ../test-real-page.json
# Back to project root for function testing
cd ..
npx sanity functions test stale-products-analysis --file test-real-page.json --dataset production --with-user-token
The function includes comprehensive logging. Check the output for:
// Function logs include:
console.log('📄 Page Product Age Analysis Function called at', new Date().toISOString())
console.log('🔍 Analyzing product ages for page:', _id)
console.log(
'📦 Found products:',
uniqueProducts.map((p) => p._id),
)
console.log('📈 Product age analysis:', {totalProducts, oldProducts, averageAge})
npx sanity documents query to find suitable test documentspage document type with modules field (existing)product document type (existing)page schema with productAgeAnalysis field (add using schema above)When a page document is created or updated, the function automatically:
Sample input document:
{
"_type": "page",
"_id": "page-123",
"title": "Page with Product Grid",
"modules": [
{
"_type": "grid",
"items": [
{
"_type": "productReference",
"productWithVariant": {
"product": {
"_ref": "product-123",
"_type": "reference"
}
}
}
]
}
]
}
Result:
productAgeAnalysis field on pageUpdate the age threshold by modifying the stale product logic:
// Change the stale product threshold from 30 to 60 days
const isOld = createdAgeInDays > 60
// Or use a different calculation based on last update
const isOld = updatedAgeInDays > 14 // 2 weeks since last update
Extend the analysis to include more metrics:
const productAgeAnalysis = uniqueProducts.map((product) => {
// ... existing logic
return {
// ... existing fields
isVeryOld: createdAgeInDays > 90, // Flag very old products
category: getProductCategory(product), // Add category analysis
lastModifiedBy: product.lastModifiedBy, // Track who last modified
stalenessScore: calculateStalenessScore(createdAgeInDays, updatedAgeInDays),
}
})
Extend the function to analyze products in different module types:
page.modules?.forEach((module) => {
// Handle different module types
if (module._type === 'grid' && module.items) {
// ... existing grid logic
} else if (module._type === 'carousel' && module.products) {
// Handle carousel modules with direct product references
module.products.forEach((productRef) => {
if (productRef._ref) {
// Add to products array
}
})
}
})
Implement different age calculation strategies:
// Weight creation vs update dates differently
const weightedAge = createdAgeInDays * 0.7 + updatedAgeInDays * 0.3
// Use different thresholds for different product types
const getAgeThreshold = (product: ProductWithDates) => {
if (product.store?.productType === 'seasonal') return 14 // 2 weeks for seasonal
if (product.store?.productType === 'evergreen') return 90 // 3 months for evergreen
return 30 // Default 30 days
}
Error: "Page not found"
_idError: "No product references found in page modules"
Function processes page but no analysis data appears
_createdAt and _updatedAt fieldsAnalysis shows incorrect ages
_createdAt and _updatedAt timestampsFunction times out during execution