Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Complete debugging and troubleshooting guide for Shopify including Liquid errors, theme preview debugging, API error handling, JavaScript console debugging, network request inspection, cart issues, checkout problems, and common error codes. Use when debugging Liquid syntax errors, troubleshooting theme rendering issues, fixing API errors, debugging JavaScript, investigating cart problems, or resolving webhook failures.
.claude/skills/microck-shopify-debugging/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-08 | ✗→✓ | ▲ Improved | 220% | 0% |
| case-18 | ✓→✗ | ▼ Worse | 463% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 544% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 434% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 766% | 0% |
Expert guidance for debugging Shopify themes, apps, and API integrations with practical solutions to common issues.
Invoke this skill when:
Debug Liquid template errors and rendering issues.
Enable Theme Preview:
1. Go to Online Store > Themes
2. Click "Customize" on your theme
3. Open browser DevTools (F12)
4. Check Console for Liquid errorsCommon Liquid Errors:
Syntax Error:
liquid{# ❌ Error: Missing endif #} {% if product.available %} <button>Add to Cart</button> {# Missing {% endif %} #} {# ✅ Fixed #} {% if product.available %} <button>Add to Cart</button> {% endif %}
Undefined Variable:
liquid{# ❌ Error: product undefined on collection page #} {{ product.title }} {# ✅ Fixed: Check context #} {% if product %} {{ product.title }} {% else %} {# Not on product page #} {% endif %}
Invalid Filter:
liquid{# ❌ Error: Unknown filter #} {{ product.price | format_money }} {# ✅ Fixed: Correct filter name #} {{ product.price | money }}
Debug Output:
liquid{# Output variable as JSON #} {{ product | json }} {# Check variable type #} {{ product.class }} {# Check if variable exists #} {% if product %} Product exists {% else %} Product is nil {% endif %} {# Output all properties #} <pre>{{ product | json }}</pre>
Console Logging from Liquid:
liquid<script> console.log('Product ID:', {{ product.id }}); console.log('Product data:', {{ product | json }}); console.log('Cart:', {{ cart | json }}); </script>
Debug JavaScript errors in themes and apps.
Browser Console:
javascript// Log to console console.log('Debug:', variable); console.error('Error:', error); console.warn('Warning:', warning); // Log object properties console.table(data); // Group related logs console.group('Cart Operations'); console.log('Cart ID:', cartId); console.log('Items:', items); console.groupEnd(); // Time operations console.time('API Call'); await fetch('/api/data'); console.timeEnd('API Call'); // Stack trace console.trace('Execution path');
Breakpoints:
javascript// Programmatic breakpoint debugger; // Set in browser DevTools: // Sources tab > Click line number
Error Handling:
javascript// ❌ Unhandled error const data = await fetch('/api/data').then(r => r.json()); // ✅ Proper error handling try { const response = await fetch('/api/data'); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); console.log('Data:', data); } catch (error) { console.error('Failed to fetch data:', error); // Show user-friendly error message alert('Failed to load data. Please try again.'); }
Network Debugging:
1. Open DevTools > Network tab
2. Filter by XHR or Fetch
3. Click request to see:
- Request headers
- Request payload
- Response headers
- Response body
- Timing informationDebug GraphQL and REST API errors.
GraphQL Errors:
Error response format:
json{ "errors": [ { "message": "Field 'invalidField' doesn't exist on type 'Product'", "locations": [{ "line": 3, "column": 5 }], "path": ["product", "invalidField"], "extensions": { "code": "FIELD_NOT_FOUND", "typeName": "Product" } } ], "data": null }
Check for errors BEFORE accessing data:
javascriptconst response = await fetch(graphqlEndpoint, { method: 'POST', headers: { 'X-Shopify-Access-Token': accessToken, 'Content-Type': 'application/json', }, body: JSON.stringify({ query, variables }), }); const { data, errors } = await response.json(); // ✅ Always check errors first if (errors) { console.error('GraphQL Errors:'); errors.forEach(error => { console.error('Message:', error.message); console.error('Location:', error.locations); console.error('Path:', error.path); console.error('Code:', error.extensions?.code); }); throw new Error(errors[0].message); } // Now safe to use data console.log('Products:', data.products);
Common GraphQL Errors:
Authentication Error:
json{ "errors": [{ "message": "Access denied", "extensions": { "code": "UNAUTHENTICATED" } }] }
Fix: Check access token:
javascript// Verify token is valid const token = 'shpat_...'; // Check token format (should start with shpat_) if (!token.startsWith('shpat_')) { console.error('Invalid token format'); } // Verify in headers headers: { 'X-Shopify-Access-Token': token, // ✅ Correct header 'Authorization': `Bearer ${token}`, // ❌ Wrong for Admin API }
Field Not Found:
json{ "errors": [{ "message": "Field 'invalidField' doesn't exist on type 'Product'" }] }
Fix: Check field name in API docs:
graphql# ❌ Wrong field name query { product(id: "gid://shopify/Product/123") { invalidField } } # ✅ Correct field name query { product(id: "gid://shopify/Product/123") { title handle status } }
Rate Limit Error:
javascript// Check rate limit header const response = await fetch(graphqlEndpoint, options); const rateLimit = response.headers.get('X-Shopify-GraphQL-Admin-Api-Call-Limit'); console.log('Rate limit:', rateLimit); // "42/50" if (response.status === 429) { const retryAfter = response.headers.get('Retry-After'); console.log(`Rate limited. Retry after ${retryAfter} seconds`); }
REST API Errors:
404 Not Found:
javascriptconst response = await fetch(`https://${shop}/admin/api/2025-10/products/999999.json`, { headers: { 'X-Shopify-Access-Token': token }, }); if (response.status === 404) { console.error('Product not found'); // Check: // 1. Product ID is correct // 2. Product exists in store // 3. Using correct endpoint }
422 Unprocessable Entity:
json{ "errors": { "title": ["can't be blank"], "price": ["must be greater than 0"] } }
Fix: Validate input:
javascriptconst response = await fetch(endpoint, { method: 'POST', headers: { 'X-Shopify-Access-Token': token, 'Content-Type': 'application/json', }, body: JSON.stringify({ product: { title: '', // ❌ Empty title price: -10, // ❌ Negative price }, }), }); if (response.status === 422) { const { errors } = await response.json(); console.error('Validation errors:', errors); // Fix data const validProduct = { title: 'Product Name', // ✅ Valid title price: 19.99, // ✅ Valid price }; }
Debug cart and Ajax API issues.
Cart Not Updating:
javascript// ❌ Common mistake: Wrong variant ID fetch('/cart/add.js', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: '123', // ❌ Wrong: using product ID instead of variant ID quantity: 1, }), }); // ✅ Fixed: Use variant ID fetch('/cart/add.js', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: 123456789, // ✅ Variant ID (numeric) quantity: 1, }), }) .then(response => { if (!response.ok) { return response.json().then(err => { console.error('Cart error:', err); throw err; }); } return response.json(); }) .then(item => { console.log('Added to cart:', item); // Update cart UI }) .catch(error => { console.error('Failed to add to cart:', error); });
Get Current Cart:
javascript// Debug current cart state fetch('/cart.js') .then(r => r.json()) .then(cart => { console.log('Cart:', cart); console.log('Item count:', cart.item_count); console.log('Total:', cart.total_price); console.log('Items:', cart.items); cart.items.forEach(item => { console.log('Item:', item.product_id, item.variant_id, item.quantity); }); });
Cart AJAX Errors:
javascript// Common error: Insufficient inventory { "status": 422, "message": "You can only add 5 of this item to your cart", "description": "Cannot add more than 5 to cart" } // Fix: Check inventory before adding const variant = product.variants.find(v => v.id === variantId); if (variant.inventory_quantity < quantity) { alert(`Only ${variant.inventory_quantity} available`); } else { // Add to cart }
Debug issues in the theme customizer.
Theme Editor Console:
1. Open theme customizer
2. Open DevTools (F12)
3. Check Console for errors
4. Look for:
- Liquid errors (red text)
- JavaScript errors
- Network failuresSection Not Rendering:
liquid{# Check section schema #} {% schema %} { "name": "My Section", "settings": [...] {# ✅ Must have settings #} } {% endschema %} {# ❌ Missing schema = won't show in customizer #}
Settings Not Updating:
liquid{# ❌ Wrong: Using hardcoded value #} <h1>Hardcoded Title</h1> {# ✅ Fixed: Use setting #} <h1>{{ section.settings.title }}</h1> {% schema %} { "name": "Hero", "settings": [ { "type": "text", "id": "title", "label": "Title", "default": "Welcome" } ] } {% endschema %}
Block Attributes Missing:
liquid{# ❌ Missing shopify_attributes #} <div class="block"> {{ block.settings.text }} </div> {# ✅ Fixed: Add shopify_attributes for theme editor #} <div class="block" {{ block.shopify_attributes }}> {{ block.settings.text }} </div>
Debug webhook delivery and processing.
Webhook Not Received:
Check in Shopify Admin:
1. Settings > Notifications > Webhooks
2. Click webhook
3. Check "Recent deliveries"
4. Look for delivery status:
- ✅ Success (200 OK)
- ❌ Failed (4xx/5xx errors)Verify Webhook HMAC:
javascriptimport crypto from 'crypto'; function verifyWebhook(body, hmac, secret) { const hash = crypto .createHmac('sha256', secret) .update(body, 'utf8') .digest('base64'); return hash === hmac; } // Express example app.post('/webhooks/orders', (req, res) => { const hmac = req.headers['x-shopify-hmac-sha256']; const body = JSON.stringify(req.body); if (!verifyWebhook(body, hmac, process.env.SHOPIFY_WEBHOOK_SECRET)) { console.error('Invalid webhook HMAC'); return res.status(401).send('Unauthorized'); } console.log('Webhook verified'); // Process webhook const order = req.body; console.log('Order:', order.id, order.email); // Respond quickly (< 5 seconds) res.status(200).send('OK'); });
Webhook Timeout:
javascript// ❌ Processing takes too long (> 5 seconds) app.post('/webhooks/orders', async (req, res) => { await processOrder(req.body); // Slow operation res.send('OK'); // Response delayed }); // ✅ Respond immediately, process async app.post('/webhooks/orders', async (req, res) => { const order = req.body; // Respond quickly res.status(200).send('OK'); // Process in background processOrder(order).catch(console.error); });
Liquid Errors:
Error: Liquid syntax error: Unknown tag 'section'
Fix: Use {% section %} only in JSON templates, not .liquid filesError: undefined method 'title' for nil:NilClass
Fix: Variable is nil. Add {% if %} check or provide default:
{{ product.title | default: "No title" }}Error: Exceeded maximum number of allowed iterations
Fix: Infinite loop detected. Check loop conditions.JavaScript Errors:
TypeError: Cannot read property 'forEach' of undefined
Fix: Array is undefined. Check:
if (items && Array.isArray(items)) {
items.forEach(item => { ... });
}ReferenceError: $ is not defined
Fix: jQuery not loaded or script runs before jQuery loadsSyntaxError: Unexpected token <
Fix: API returned HTML error page instead of JSON. Check API endpoint.API Errors:
Access denied - check your access scopes
Fix: App needs additional permissions. Update scopes in Partner Dashboard.Throttled: Exceeded API rate limit
Fix: Implement rate limit handling with exponential backoff.Field doesn't exist on type
Fix: Check API version and field availability in docs.Browser DevTools:
- Console: View errors and logs
- Network: Inspect API requests
- Sources: Set breakpoints
- Application: View cookies, localStorage
- Performance: Profile page loadShopify Tools:
- Theme Preview: Test changes before publishing
- Theme Inspector: View section data
- API Explorer: Test GraphQL queries
- Webhook Logs: Check delivery statusUseful Console Commands:
javascript// Get all form data new FormData(document.querySelector('form')) // View all cookies document.cookie // Check localStorage localStorage // View all global variables console.log(window) // Get computed styles getComputedStyle(element)
javascript// Debug Liquid {{ variable | json }} {% if variable %}{{ variable }}{% endif %} // Debug JavaScript console.log('Debug:', data); debugger; // Debug API const { data, errors } = await response.json(); if (errors) console.error(errors); // Debug Cart fetch('/cart.js').then(r => r.json()).then(console.log); // Debug Webhooks const valid = verifyHmac(body, hmac, secret); console.log('Webhook valid:', valid);
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 4,292 | 2,914 | -32% | 1 | 1 | 0% | 742 | 4,778 | +544% | 0 | 0 | — |
case-02 | pass→pass | 5,189 | 3,813 | -27% | 1 | 1 | 0% | 931 | 4,967 | +434% | 0 | 0 | — |
case-03 | pass→pass | 3,320 | 4,547 | +37% | 1 | 1 | 0% | 588 | 5,090 | +766% | 0 | 0 | — |
case-04 | pass→pass | 14,086 | 8,402 | -40% | 1 | 1 | 0% | 2,324 | 5,697 | +145% | 0 | 0 | — |
case-05 | pass→pass | 4,218 | 4,844 | +15% | 1 | 1 | 0% | 731 | 5,048 | +591% | 0 | 0 | — |
case-06 | pass→pass | 3,182 | 3,288 | +3% | 1 | 1 | 0% | 557 | 4,855 | +772% | 0 | 0 | — |
case-07 | pass→pass | 17,611 | 9,391 | -47% | 1 | 1 | 0% | 2,070 | 6,066 | +193% | 0 | 0 | — |
case-08 | fail→pass | 9,992 | 5,660 | -43% | 1 | 1 | 0% | 1,654 | 5,293 | +220% | 0 | 0 | — |
case-09 | pass→pass | 8,511 | 5,206 | -39% | 1 | 1 | 0% | 1,257 | 5,177 | +312% | 0 | 0 | — |
case-10 | pass→pass | 12,628 | 8,260 | -35% | 1 | 1 | 0% | 1,959 | 5,697 | +191% | 0 | 0 | — |
case-11 | pass→pass | 3,234 | 3,577 | +11% | 1 | 1 | 0% | 609 | 4,916 | +707% | 0 | 0 | — |
case-12 | pass→pass | 8,228 | 9,231 | +12% | 1 | 1 | 0% | 1,345 | 5,857 | +335% | 0 | 0 | — |
case-13 | pass→pass | 3,991 | 3,694 | -7% | 1 | 1 | 0% | 710 | 4,944 | +596% | 0 | 0 | — |
case-14 | pass→pass | 3,772 | 2,352 | -38% | 1 | 1 | 0% | 548 | 4,635 | +746% | 0 | 0 | — |
case-15 | pass→pass | 5,115 | 2,897 | -43% | 1 | 1 | 0% | 795 | 4,718 | +493% | 0 | 0 | — |
case-16 | pass→pass | 10,607 | 7,930 | -25% | 1 | 1 | 0% | 1,715 | 5,608 | +227% | 0 | 0 | — |
case-17 | pass→pass | 4,003 | 3,000 | -25% | 1 | 1 | 0% | 711 | 4,815 | +577% | 0 | 0 | — |
case-18 | pass→fail | 6,279 | 6,360 | +1% | 1 | 1 | 0% | 954 | 5,375 | +463% | 0 | 0 | — |
case-19 | pass→pass | 21,268 | 22,029 | +4% | 1 | 1 | 0% | 3,480 | 7,874 | +126% | 0 | 0 | — |
case-20 | pass→pass | 16,210 | 18,122 | +12% | 1 | 1 | 0% | 3,224 | 8,184 | +154% | 0 | 0 | — |
case-21 | pass→pass | 18,472 | 15,747 | -15% | 1 | 1 | 0% | 3,413 | 7,605 | +123% | 0 | 0 | — |
case-22 | pass→pass | 10,475 | 8,667 | -17% | 1 | 1 | 0% | 1,976 | 6,112 | +209% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of 0 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.