Shopify Debugging & Troubleshooting
Expert guidance for debugging Shopify themes, apps, and API integrations with practical solutions to common issues.
When to Use This Skill
Invoke this skill when:
-
Debugging Liquid template errors or syntax issues
-
Troubleshooting theme rendering problems
-
Fixing API errors (GraphQL, REST)
-
Debugging JavaScript errors in themes or apps
-
Investigating cart or checkout issues
-
Resolving webhook delivery failures
-
Troubleshooting OAuth authentication problems
-
Debugging theme preview or customizer issues
-
Investigating slow page loads or performance problems
-
Fixing metafield or metaobject access errors
-
Resolving CORS or network request issues
Core Capabilities
- Liquid Debugging
Debug Liquid template errors and rendering issues.
Enable Theme Preview:
- Go to Online Store > Themes
- Click "Customize" on your theme
- Open browser DevTools (F12)
- Check Console for Liquid errors
Common Liquid Errors:
Syntax Error:
{# ❌ 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:
{# ❌ Error: product undefined on collection page #} {{ product.title }}
{# ✅ Fixed: Check context #} {% if product %} {{ product.title }} {% else %} {# Not on product page #} {% endif %}
Invalid Filter:
{# ❌ Error: Unknown filter #} {{ product.price | format_money }}
{# ✅ Fixed: Correct filter name #} {{ product.price | money }}
Debug Output:
{# 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:
<script> console.log('Product ID:', {{ product.id }}); console.log('Product data:', {{ product | json }}); console.log('Cart:', {{ cart | json }}); </script>
- JavaScript Debugging
Debug JavaScript errors in themes and apps.
Browser Console:
// 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:
// Programmatic breakpoint debugger;
// Set in browser DevTools: // Sources tab > Click line number
Error Handling:
// ❌ 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:
-
Open DevTools > Network tab
-
Filter by XHR or Fetch
-
Click request to see:
- Request headers
- Request payload
- Response headers
- Response body
- Timing information
-
API Error Debugging
Debug GraphQL and REST API errors.
GraphQL Errors:
Error response format:
{ "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:
const 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:
{ "errors": [{ "message": "Access denied", "extensions": { "code": "UNAUTHENTICATED" } }] }
Fix: Check access token:
// 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:
{ "errors": [{ "message": "Field 'invalidField' doesn't exist on type 'Product'" }] }
Fix: Check field name in API docs:
❌ 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:
// 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:
const 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:
{ "errors": { "title": ["can't be blank"], "price": ["must be greater than 0"] } }
Fix: Validate input:
const 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 }; }
- Cart Debugging
Debug cart and Ajax API issues.
Cart Not Updating:
// ❌ 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:
// 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:
// 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
}
- Theme Preview Debugging
Debug issues in the theme customizer.
Theme Editor Console:
- Open theme customizer
- Open DevTools (F12)
- Check Console for errors
- Look for:
- Liquid errors (red text)
- JavaScript errors
- Network failures
Section Not Rendering:
{# Check section schema #} {% schema %} { "name": "My Section", "settings": [...] {# ✅ Must have settings #} } {% endschema %}
{# ❌ Missing schema = won't show in customizer #}
Settings Not Updating:
{# ❌ 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:
{# ❌ 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>
- Webhook Debugging
Debug webhook delivery and processing.
Webhook Not Received:
Check in Shopify Admin:
- Settings > Notifications > Webhooks
- Click webhook
- Check "Recent deliveries"
- Look for delivery status:
- ✅ Success (200 OK)
- ❌ Failed (4xx/5xx errors)
Verify Webhook HMAC:
import 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:
// ❌ 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); });
- Common Error Messages
Liquid Errors:
Error: Liquid syntax error: Unknown tag 'section' Fix: Use {% section %} only in JSON templates, not .liquid files
Error: 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 loads
SyntaxError: 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.
Debugging Toolkit
Browser DevTools:
- Console: View errors and logs
- Network: Inspect API requests
- Sources: Set breakpoints
- Application: View cookies, localStorage
- Performance: Profile page load
Shopify Tools:
- Theme Preview: Test changes before publishing
- Theme Inspector: View section data
- API Explorer: Test GraphQL queries
- Webhook Logs: Check delivery status
Useful Console Commands:
// 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)
Best Practices
-
Always check for errors before accessing data (API responses)
-
Use try-catch blocks for all async operations
-
Log meaningful messages with context
-
Verify HMAC for all webhooks
-
Test in theme preview before publishing
-
Monitor API rate limits and implement backoff
-
Handle edge cases (nil values, empty arrays)
-
Use browser DevTools for network debugging
-
Check API version compatibility
-
Validate input before API calls
Integration with Other Skills
-
shopify-liquid - Use when debugging Liquid template code
-
shopify-api - Use when debugging API calls
-
shopify-theme-dev - Use when debugging theme structure issues
-
shopify-app-dev - Use when debugging custom apps
-
shopify-performance - Use when debugging slow performance
Quick Reference
// 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);