Help us improve
Share bugs, ideas, or general feedback.
From wix-ecom-cowork
Audits Wix store product listings for missing descriptions, images, prices, SKUs, brands, ribbons; flags short descriptions; computes catalog health scores via REST API queries and jq.
npx claudepluginhub wix/wix-ecom-cowork --plugin wix-ecom-coworkHow this skill is triggered — by the user, by Claude, or both
Slash command
/wix-ecom-cowork:catalog-optimizationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Product listing quality audit, SEO improvements, missing field detection, and catalog health scoring using direct Wix REST API calls.
Audits a WooCommerce store's product catalog for AI readiness, scoring completeness, schema coverage, policies, and content quality with actionable recommendations.
Audits Wix stores via API: scores catalog health for missing images, descriptions, prices; checks inventory for low/out-of-stock items; outputs scores and status via bash script.
Scores Shopify products 0-100 on data completeness across description, images, SEO title/description, variant weight/barcode/cost, and metafields. Ranks lowest-scoring products for data fixes.
Share bugs, ideas, or general feedback.
Product listing quality audit, SEO improvements, missing field detection, and catalog health scoring using direct Wix REST API calls.
df7c18eb-009b-4868-9891-15e19dddbe67${API_KEY}${SITE_ID}API Call:
curl -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}' | jq '[.products[] | select(.description == null or .description == "" or (.description | length) < 50)] | map({id, name, description_length: (.description // "" | length)})'
curl -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}' | jq '[.products[] | select(.media.mainMedia == null or .media.mainMedia.image == null)] | map({id, name, has_image: false})'
curl -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}' | jq '[.products[] | select(.price == null or (.price | tonumber) == 0)] | map({id, name, price})'
curl -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}' | jq '[.products[] | select(.sku == null or .sku == "")] | map({id, name, sku})'
#!/bin/bash
products=$(curl -s -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}')
echo "CATALOG HEALTH REPORT"
echo "===================="
echo ""
# Calculate quality scores
echo "$products" | jq -r '
[.products[] | {
id,
name,
has_description: ((.description // "" | length) >= 50),
has_image: (.media.mainMedia.image != null),
has_price: ((.price // 0 | tonumber) > 0),
has_sku: (.sku != null and .sku != ""),
has_brand: (.brand != null and .brand != ""),
has_ribbon: (.ribbon != null and .ribbon != ""),
description_length: (.description // "" | length)
}] |
map(. + {
quality_score: (
(if .has_description then 25 else 0 end) +
(if .has_image then 25 else 0 end) +
(if .has_price then 20 else 0 end) +
(if .has_sku then 15 else 0 end) +
(if .has_brand then 10 else 0 end) +
(if .has_ribbon then 5 else 0 end)
)
}) |
# Overall catalog metrics
{
total_products: length,
avg_quality_score: ([.[].quality_score] | add / length),
excellent: ([.[] | select(.quality_score >= 80)] | length),
good: ([.[] | select(.quality_score >= 60 and .quality_score < 80)] | length),
needs_improvement: ([.[] | select(.quality_score >= 40 and .quality_score < 60)] | length),
poor: ([.[] | select(.quality_score < 40)] | length),
missing_descriptions: ([.[] | select(.has_description == false)] | length),
missing_images: ([.[] | select(.has_image == false)] | length),
missing_skus: ([.[] | select(.has_sku == false)] | length),
missing_brands: ([.[] | select(.has_brand == false)] | length)
} |
"Total Products: \(.total_products)",
"Average Quality Score: \(.avg_quality_score | floor)/100",
"",
"Score Distribution:",
" Excellent (80-100): \(.excellent) products",
" Good (60-79): \(.good) products",
" Needs Improvement (40-59): \(.needs_improvement) products",
" Poor (0-39): \(.poor) products",
"",
"Missing Fields:",
" Descriptions: \(.missing_descriptions)",
" Images: \(.missing_images)",
" SKUs: \(.missing_skus)",
" Brands: \(.missing_brands)"
'
#!/bin/bash
products=$(curl -s -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}')
echo "TOP PRIORITY PRODUCTS TO FIX"
echo "============================"
echo ""
echo "$products" | jq -r '
[.products[] | {
id,
name,
has_description: ((.description // "" | length) >= 50),
has_image: (.media.mainMedia.image != null),
has_price: ((.price // 0 | tonumber) > 0),
quality_score: (
(if ((.description // "" | length) >= 50) then 25 else 0 end) +
(if (.media.mainMedia.image != null) then 25 else 0 end) +
(if ((.price // 0 | tonumber) > 0) then 20 else 0 end) +
(if (.sku != null and .sku != "") then 15 else 0 end) +
(if (.brand != null and .brand != "") then 10 else 0 end) +
(if (.ribbon != null and .ribbon != "") then 5 else 0 end)
),
issues: [
(if ((.description // "" | length) < 50) then "❌ Missing/short description" else null end),
(if (.media.mainMedia.image == null) then "❌ No product image" else null end),
(if ((.price // 0 | tonumber) == 0) then "❌ No price set" else null end),
(if (.sku == null or .sku == "") then "⚠️ Missing SKU" else null end),
(if (.brand == null or .brand == "") then "⚠️ Missing brand" else null end)
] | map(select(. != null))
}] |
map(select(.quality_score < 60)) |
sort_by(.quality_score) |
.[:10] |
.[] |
"\(.name) (Score: \(.quality_score)/100)",
(.issues | map(" " + .) | .[]),
""
'
curl -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}' | jq '[.products[] | select(.seoData == null or .seoData.tags == null or (.seoData.tags | length) == 0)] | map({id, name, has_seo: false})'
#!/bin/bash
# Get product
PRODUCT_ID="product-abc123"
product=$(curl -s -X GET "https://www.wixapis.com/stores/v1/products/${PRODUCT_ID}" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}")
product_name=$(echo "$product" | jq -r '.product.name')
product_desc=$(echo "$product" | jq -r '.product.description // ""')
# Generate SEO metadata (example - use AI or template)
seo_title="${product_name} - Buy Online | Your Store Name"
seo_desc=$(echo "$product_desc" | cut -c1-155)
echo "Generated SEO for: $product_name"
echo "Title: $seo_title"
echo "Description: $seo_desc"
echo ""
echo "Apply with:"
echo "curl -X PATCH \"https://www.wixapis.com/stores/v1/products/${PRODUCT_ID}\" \\"
echo " -H \"Authorization: \${API_KEY}\" \\"
echo " -H \"wix-site-id: \${SITE_ID}\" \\"
echo " -H \"Content-Type: application/json\" \\"
echo " -d '{
\"product\": {
\"seoData\": {
\"tags\": [
{\"type\": \"title\", \"children\": \"'"$seo_title"'\", \"custom\": true},
{\"type\": \"meta\", \"props\": {\"name\": \"description\", \"content\": \"'"$seo_desc"'\"}}
]
}
}
}'"
#!/bin/bash
products=$(curl -s -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}')
echo "Products with Non-Optimized Slugs"
echo "================================="
echo ""
echo "$products" | jq -r '
.products[] |
select(.slug | contains("product-") or contains("_") or contains("--")) |
"❌ \(.name) → /product-page/\(.slug)"
'
curl -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}' | jq '[.products[] | {id, name, image_count: ((.media.items // []) | length)}] | sort_by(.image_count)'
#!/bin/bash
products=$(curl -s -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}')
echo "Products Needing More Images (< 3 images)"
echo "========================================="
echo ""
echo "$products" | jq -r '
.products[] |
{
id,
name,
price,
image_count: ((.media.items // []) | length)
} |
select(.image_count < 3) |
"• \(.name) - \(.image_count) image(s) - $\(.price)"
'
#!/bin/bash
products=$(curl -s -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}')
echo "Products Missing Image Alt Text"
echo "==============================="
echo ""
echo "$products" | jq -r '
.products[] |
select(.media.mainMedia.image != null) |
select(.media.mainMedia.image.altText == null or .media.mainMedia.image.altText == "") |
"• \(.name) - Main image has no alt text"
'
curl -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}' | jq '[.products[] | select(.comparePrice == null or (.comparePrice | tonumber) == 0)] | map({id, name, price, comparePrice})'
#!/bin/bash
products=$(curl -s -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}')
echo "Pricing Issues"
echo "=============="
echo ""
echo "$products" | jq -r '
.products[] |
{
name,
price: (.price // 0 | tonumber),
comparePrice: (.comparePrice // 0 | tonumber),
cost: (.cost // 0 | tonumber)
} |
# Check for various pricing issues
if (.price == 0) then
"❌ \(.name) - Price is $0"
elif (.comparePrice > 0 and .comparePrice <= .price) then
"⚠️ \(.name) - Compare price (\(.comparePrice)) not higher than price (\(.price))"
elif (.cost > 0 and .price < .cost) then
"❌ \(.name) - Selling below cost! Price: $\(.price), Cost: $\(.cost)"
elif (.cost > 0 and ((.price - .cost) / .price * 100) < 20) then
"⚠️ \(.name) - Low margin: \(((.price - .cost) / .price * 100) | floor)%"
else
empty
end
'
#!/bin/bash
products=$(curl -s -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}')
collections=$(curl -s -X POST "https://www.wixapis.com/stores/v1/collections/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{"query": {"paging": {"limit": 50}}}')
echo "Average Margins by Collection"
echo "============================="
echo ""
echo "$products" | jq -r --argjson cols "$(echo "$collections" | jq '.collections')" '
[.products[] | {
name,
price: (.price // 0 | tonumber),
cost: (.cost // 0 | tonumber),
margin: (if (.cost // 0 | tonumber) > 0 and (.price // 0 | tonumber) > 0 then ((.price - .cost) / .price * 100) else null end),
collectionIds: .collectionIds // []
}] |
map(select(.margin != null)) |
# Group by first collection
group_by(.collectionIds[0]) |
map({
collection_id: (.[0].collectionIds[0] // "No Collection"),
avg_margin: ([.[].margin] | add / length),
products: length
}) |
.[] |
"\(.collection_id): \(.avg_margin | floor)% avg margin (\(.products) products)"
'
#!/bin/bash
products=$(curl -s -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}')
echo "Products with Short Descriptions (< 100 chars)"
echo "=============================================="
echo ""
echo "$products" | jq -r '
.products[] |
{
name,
desc_length: (.description // "" | length)
} |
select(.desc_length < 100) |
"• \(.name) - \(.desc_length) characters"
'
#!/bin/bash
products=$(curl -s -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}')
echo "Products Missing Key Fields"
echo "==========================="
echo ""
echo "$products" | jq -r '
.products[] |
{
name,
missing: [
(if (.description == null or (.description | length) < 50) then "description" else null end),
(if (.brand == null or .brand == "") then "brand" else null end),
(if (.weight == null or (.weight | tonumber) == 0) then "weight" else null end),
(if (.ribbon == null or .ribbon == "") then "ribbon/badge" else null end),
(if (.sku == null or .sku == "") then "SKU" else null end)
] | map(select(. != null))
} |
select(.missing | length > 0) |
"• \(.name): Missing \(.missing | join(", "))"
'
#!/bin/bash
products=$(curl -s -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}')
echo "$products" | jq -r '.products[] | select(.seoData == null) | .id' | while read product_id; do
# Get product details
product=$(curl -s -X GET "https://www.wixapis.com/stores/v1/products/${product_id}" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}")
name=$(echo "$product" | jq -r '.product.name')
desc=$(echo "$product" | jq -r '.product.description // ""' | cut -c1-155)
# Update with SEO data
curl -s -X PATCH "https://www.wixapis.com/stores/v1/products/${product_id}" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d "{
\"product\": {
\"seoData\": {
\"tags\": [
{\"type\": \"title\", \"children\": \"$name - Buy Online\", \"custom\": true},
{\"type\": \"meta\", \"props\": {\"name\": \"description\", \"content\": \"$desc\"}}
]
}
}
}" > /dev/null
echo "✓ Added SEO data to: $name"
sleep 0.2
done
#!/bin/bash
products=$(curl -s -X POST "https://www.wixapis.com/stores/v1/products/query" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d '{
"query": {
"filter": "{\"visible\": true}",
"paging": {"limit": 100}
}
}')
echo "$products" | jq -r '.products[] | select(.media.mainMedia.image != null and (.media.mainMedia.image.altText == null or .media.mainMedia.image.altText == "")) | {id, name, image: .media.mainMedia.image.url}' | jq -c '.' | while read product; do
product_id=$(echo "$product" | jq -r '.id')
product_name=$(echo "$product" | jq -r '.name')
image_url=$(echo "$product" | jq -r '.image')
# Update with alt text
curl -s -X PATCH "https://www.wixapis.com/stores/v1/products/${product_id}" \
-H "Authorization: ${API_KEY}" \
-H "wix-site-id: ${SITE_ID}" \
-H "Content-Type: application/json" \
-d "{
\"product\": {
\"media\": {
\"mainMedia\": {
\"image\": {
\"url\": \"$image_url\",
\"altText\": \"$product_name\"
}
}
}
}
}" > /dev/null
echo "✓ Added alt text to: $product_name"
sleep 0.2
done