
Research
Malicious npm Packages Impersonate Flashbots SDKs, Targeting Ethereum Wallet Credentials
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
@multidots/sanity-plugin-woocommerce-sync
Advanced tools
A professional Sanity v4 plugin that allows you to sync your WooCommerce products to Sanity Studio
A professional Sanity Studio v4 plugin that allows you to sync your WooCommerce products to Sanity Studio. This plugin provides a clean, enterprise-grade solution for WooCommerce integration with comprehensive error handling, real-time form updates, and modern React components.
npm install @multidots/sanity-plugin-woocommerce-sync
// sanity.config.ts
import { defineConfig } from 'sanity'
import { woocommerceSyncPlugin } from '@multidots/sanity-plugin-woocommerce-sync'
export default defineConfig({
// ... other config
plugins: [
woocommerceSyncPlugin(),
// ... other plugins
],
})
CRITICAL: You must set up these environment variables in your Sanity Studio root .env.local
file:
# .env.local (in Sanity Studio root)
SANITY_STUDIO_WOOCOMMERCE_API_URL=http://localhost:3001
SANITY_STUDIO_WOOCOMMERCE_TEST_CONNECTION_PATH=/api/woocommerce/test-connection
SANITY_STUDIO_WOOCOMMERCE_FETCH_PRODUCT_PATH=/api/woocommerce/fetch-product
Important Notes:
SANITY_STUDIO_
to be accessible in Sanity StudioGo to Structure → WooCommerce Settings
Fill in your WooCommerce API credentials:
https://yourstore.com
)Test Connection - Use the "Test WooCommerce Connection" button to verify your credentials
Debug Document - Use the "Debug Document" button to inspect configuration state
woocommerce.settings
)A singleton schema that stores your WooCommerce API configuration:
{
storeUrl: string // Store URL (required)
consumerKey: string // API Consumer Key (required)
consumerSecret: string // API Consumer Secret (required)
testProductId: number // Test product ID for connection testing (required)
}
woocommerce.product
)Comprehensive product data structure with all essential fields:
{
wooId: number // WooCommerce Product ID (required)
title: string // Product title
slug: slug // Product slug (auto-generated from title)
primaryImage: string // Primary image URL
permalink: string // Direct link to product on WooCommerce
shortDescription: string // Short description (HTML stripped)
type: string // Product type: 'simple', 'variable', 'grouped', 'external'
featured: boolean // Featured product status
sku: string // Stock keeping unit
regularPrice: string // Regular price
salePrice: string // Sale price
price: string // Current selling price
averageRating: number // Average customer rating (0-5)
ratingCount: number // Number of customer ratings
stockStatus: string // Stock status: 'instock', 'outofstock', 'onbackorder'
lastSyncedAt: datetime // Last sync timestamp (read-only)
}
Your server must provide these endpoints that the plugin will call:
URL: {API_BASE_URL}{TEST_CONNECTION_PATH}
Method: POST
Body:
{
"testProductId": 123
}
Expected Response:
{
"success": true,
"testProduct": {
"id": 123,
"name": "Product Name",
"type": "simple",
"featured": false,
"sku": "PROD-123",
"regular_price": "29.99",
"sale_price": "24.99",
"price": "24.99",
"average_rating": "4.5",
"rating_count": 128,
"stock_status": "instock"
},
"storeInfo": {
"productCount": 150
}
}
URL: {API_BASE_URL}{FETCH_PRODUCT_PATH}
Method: POST
Body:
{
"productId": 123
}
Expected Response:
{
"success": true,
"product": {
"id": 123,
"name": "Product Name",
"slug": "product-name",
"type": "simple",
"featured": false,
"sku": "PROD-123",
"regular_price": "29.99",
"sale_price": "24.99",
"price": "24.99",
"average_rating": "4.5",
"rating_count": 128,
"stock_status": "instock",
"permalink": "https://yourstore.com/product/product-name",
"short_description": "Product description without HTML tags",
"images": [
{
"src": "https://yourstore.com/wp-content/uploads/image.jpg"
}
]
}
}
// pages/api/woocommerce/test-connection.ts
import { NextApiRequest, NextApiResponse } from 'next'
import WooCommerceRestApi from '@woocommerce/woocommerce-rest-api'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' })
}
try {
const { testProductId } = req.body
// Initialize WooCommerce API client
const WooCommerce = new WooCommerceRestApi({
url: process.env.WOOCOMMERCE_STORE_URL!,
consumerKey: process.env.WOOCOMMERCE_CONSUMER_KEY!,
consumerSecret: process.env.WOOCOMMERCE_CONSUMER_SECRET!,
version: 'wc/v3'
})
// Fetch test product
const productResponse = await WooCommerce.get(`products/${testProductId}`)
const product = productResponse.data
// Get store info
const productsResponse = await WooCommerce.get('products')
const productCount = productsResponse.headers['x-wp-total']
res.status(200).json({
success: true,
testProduct: {
id: product.id,
name: product.name,
type: product.type,
featured: product.featured,
sku: product.sku,
regular_price: product.regular_price,
sale_price: product.sale_price,
price: product.price,
average_rating: product.average_rating,
rating_count: product.rating_count,
stock_status: product.stock_status
},
storeInfo: {
productCount: parseInt(productCount)
}
})
} catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
})
}
}
npm install @sanity/client @sanity/image-url
// components/WooCommerceProductDisplay.tsx
import React, { useState, useEffect } from 'react'
export interface WooCommerceProduct {
_id: string
wooId: number
title: string
slug: { current: string }
primaryImage: string
permalink: string
shortDescription: string
type: 'simple' | 'variable' | 'grouped' | 'external'
featured: boolean
sku: string
regularPrice: string
salePrice: string
price: string
averageRating: number
ratingCount: number
stockStatus: 'instock' | 'outofstock' | 'onbackorder'
lastSyncedAt: string
}
export function WooCommerceProductDisplay({
referenceId,
layout = 'card'
}: {
referenceId: string
layout?: 'card' | 'horizontal' | 'minimal'
}) {
const [product, setProduct] = useState<WooCommerceProduct | null>(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
if (!referenceId) return
setLoading(true)
setError(null)
// Fetch product from your API route (not directly from Sanity to avoid CORS)
fetch('/api/woocommerce/product-lookup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ referenceId })
})
.then(response => response.json())
.then(result => {
if (result.success) {
setProduct(result.product)
} else {
setError(result.error || 'Product not found')
}
})
.catch(err => {
setError(`Failed to fetch product: ${err.message}`)
})
.finally(() => {
setLoading(false)
})
}, [referenceId])
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error}</div>
if (!product) return <div>No product found</div>
// Render product based on layout
return (
<div className="woocommerce-product">
<h3>{product.title}</h3>
<p>{product.shortDescription}</p>
<div className="price">{product.price}</div>
{/* Add more product display logic */}
</div>
)
}
// pages/api/woocommerce/product-lookup.ts
import { NextApiRequest, NextApiResponse } from 'next'
import { createClient } from '@sanity/client'
const sanityClient = createClient({
projectId: process.env.SANITY_PROJECT_ID!,
dataset: process.env.SANITY_DATASET!,
apiVersion: '2025-01-01',
useCdn: false,
token: process.env.SANITY_API_TOKEN!
})
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' })
}
try {
const { referenceId } = req.body
// Extract UUID from reference ID (remove document type prefix)
const documentId = referenceId.replace(/^[^.]+\./, '')
const product = await sanityClient.fetch(`
*[_type == "woocommerce.product" && _id == $documentId][0]{
_id,
wooId,
title,
slug,
primaryImage,
permalink,
shortDescription,
type,
featured,
sku,
regularPrice,
salePrice,
price,
averageRating,
ratingCount,
stockStatus,
lastSyncedAt
}
`, { documentId })
if (!product) {
return res.status(404).json({
success: false,
error: 'Product not found'
})
}
res.status(200).json({
success: true,
product
})
} catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
})
}
}
Error: SANITY_STUDIO_WOOCOMMERCE_API_URL environment variable is not defined
Solution:
.env.local
file in Sanity Studio rootSANITY_STUDIO_
Problem: Frontend components can't fetch data directly from Sanity Studio
Solution:
Error: "API Test Failed" or "Fetch Failed"
Solutions:
Error: "Document Not Found" when fetching products
Solution:
Error: Type mismatches or missing types
Solution:
The plugin includes built-in debugging tools:
npm run build
npm run dev
npm run clean
npm run build
MIT License.
For issues and questions:
Note: This plugin is designed for production use and follows modern Sanity v4 best practices. Ensure all environment variables are properly configured before use.
FAQs
A professional Sanity v4 plugin that allows you to sync your WooCommerce products to Sanity Studio
We found that @multidots/sanity-plugin-woocommerce-sync demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
Security News
Ruby maintainers from Bundler and rbenv teams are building rv to bring Python uv's speed and unified tooling approach to Ruby development.
Security News
Following last week’s supply chain attack, Nx published findings on the GitHub Actions exploit and moved npm publishing to Trusted Publishers.