# Authentication Source: https://quickbutik.dev/api-v1/authentication Secure your API requests with Basic Authentication The Quickbutik API uses **Basic Authentication** with API keys to authenticate requests. All API calls must be made over HTTPS, and requests made over HTTP will be rejected. ## Quick Start ```bash cURL theme={null} curl https://api.quickbutik.com/v1/orders \ -u your_api_key:your_api_key ``` ```javascript Node.js theme={null} const apiKey = 'your_api_key'; const credentials = Buffer.from(`${apiKey}:${apiKey}`).toString('base64'); fetch('https://api.quickbutik.com/v1/orders', { headers: { 'Authorization': `Basic ${credentials}` } }); ``` ```python Python theme={null} import requests import base64 api_key = 'your_api_key' credentials = base64.b64encode(f'{api_key}:{api_key}'.encode()).decode() response = requests.get( 'https://api.quickbutik.com/v1/orders', headers={'Authorization': f'Basic {credentials}'} ) ``` ```php PHP theme={null} ``` ## How it works Basic Authentication requires you to include an `Authorization` header with every request. The header value consists of the word "Basic" followed by a space and a base64-encoded string of your credentials. ### Format ``` Authorization: Basic BASE64_ENCODED_CREDENTIALS ``` ### Credential encoding Your credentials should be formatted as `api_key:api_key` and then base64-encoded. Take your API key and format it as: `your_api_key:your_api_key` Encode the formatted string using base64 encoding Include the encoded string in your request headers as: `Authorization: Basic ENCODED_STRING` ## Example Let's say your API key is `sk_live_abc123`. Here's how you'd construct the Authorization header: ```text Step 1: Format theme={null} sk_live_abc123:sk_live_abc123 ``` ```text Step 2: Base64 encode theme={null} c2tfbGl2ZV9hYmMxMjM6c2tfbGl2ZV9hYmMxMjM= ``` ```text Step 3: Authorization header theme={null} Authorization: Basic c2tfbGl2ZV9hYmMxMjM6c2tfbGl2ZV9hYmMxMjM= ``` ## Getting your API key API keys can be generated and managed in the Quickbutik Control Panel by the store owner. Navigate to **Settings β†’ API** to create and manage your API keys. ## Security best practices **Keep your API keys secure** * Never expose API keys in client-side code * Use environment variables to store API keys * Rotate API keys regularly * Only grant necessary permissions **HTTPS Required** All API requests must be made over HTTPS. Requests made over plain HTTP will be automatically rejected for security reasons. ## Migration from legacy authentication **Deprecation Notice** If you're currently using the legacy `apiKey` query parameter for authentication, please migrate to Basic Authentication as described above. The legacy method will be discontinued soon. ### Before (Legacy - Deprecated) ```bash theme={null} curl "https://api.quickbutik.com/v1/orders?apiKey=your_api_key" ``` ### After (Current) ```bash theme={null} curl https://api.quickbutik.com/v1/orders \ -u your_api_key:your_api_key ``` ## Error responses When authentication fails, you'll receive a `401 Unauthorized` response: ```json theme={null} { "code": 401, "error": "Unauthorized - Invalid or missing authentication" } ``` Common authentication errors: * Missing `Authorization` header * Invalid API key * Malformed base64 encoding * Using HTTP instead of HTTPS ## Testing your authentication You can quickly test your authentication setup with a simple API call: ```bash cURL theme={null} curl https://api.quickbutik.com/v1/products/count \ -u your_api_key:your_api_key ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.quickbutik.com/v1/products/count', { headers: { 'Authorization': `Basic ${btoa('your_api_key:your_api_key')}` } }); if (response.ok) { console.log('Authentication successful!'); const data = await response.json(); console.log(data); } else { console.log('Authentication failed'); } ``` A successful response will return your product count, confirming your authentication is working correctly. # Create category Source: https://quickbutik.dev/api-v1/categories/create-category post /v1/categories Create a new product category # Get categories Source: https://quickbutik.dev/api-v1/categories/get-categories get /v1/categories Fetch store categories # Inventory Management Source: https://quickbutik.dev/api-v1/guides/inventory-management Keep your product stock levels synchronized across platforms Learn how to build a robust inventory synchronization system that keeps stock levels accurate across Quickbutik and your external systems in real-time. **Use cases covered:** * Real-time stock level synchronization * Bulk inventory updates * Low stock alerts and management * Multi-location inventory tracking * Prevention of overselling ## Quick Example Here's a complete example of syncing inventory when stock changes: ```javascript Node.js theme={null} const QuickbutikAPI = require('./quickbutik-api'); class InventoryManager { constructor(apiKey) { this.api = new QuickbutikAPI(apiKey); } async updateProductStock(sku, newQuantity, location = 'main') { try { const result = await this.api.updateProducts({ sku: sku, stock: newQuantity, qty_location: location }); console.log(`Updated ${sku} stock to ${newQuantity} at ${location}`); return result; } catch (error) { console.error(`Failed to update stock for ${sku}:`, error); throw error; } } async bulkUpdateInventory(inventoryUpdates) { const results = []; const batchSize = 10; for (let i = 0; i < inventoryUpdates.length; i += batchSize) { const batch = inventoryUpdates.slice(i, i + batchSize); const batchPromises = batch.map(update => this.updateProductStock(update.sku, update.quantity, update.location) .catch(error => ({ error: error.message, sku: update.sku })) ); const batchResults = await Promise.all(batchPromises); results.push(...batchResults); // Small delay to avoid rate limiting if (i + batchSize < inventoryUpdates.length) { await new Promise(resolve => setTimeout(resolve, 100)); } } return results; } } // Usage const inventory = new InventoryManager(process.env.QUICKBUTIK_API_KEY); // Update single product await inventory.updateProductStock('SHIRT-123', 45); // Bulk update await inventory.bulkUpdateInventory([ { sku: 'SHIRT-123', quantity: 45, location: 'warehouse-1' }, { sku: 'PANTS-456', quantity: 12, location: 'warehouse-1' }, { sku: 'HAT-789', quantity: 0, location: 'warehouse-2' } ]); ``` ```python Python theme={null} import asyncio import time from typing import List, Dict from quickbutik_api import QuickbutikAPI class InventoryManager: def __init__(self, api_key: str): self.api = QuickbutikAPI(api_key) async def update_product_stock(self, sku: str, new_quantity: int, location: str = 'main'): try: result = await self.api.update_products({ 'sku': sku, 'stock': new_quantity, 'qty_location': location }) print(f'Updated {sku} stock to {new_quantity} at {location}') return result except Exception as error: print(f'Failed to update stock for {sku}: {error}') raise error async def bulk_update_inventory(self, inventory_updates: List[Dict]): results = [] batch_size = 10 for i in range(0, len(inventory_updates), batch_size): batch = inventory_updates[i:i + batch_size] batch_tasks = [] for update in batch: task = self.update_product_stock( update['sku'], update['quantity'], update.get('location', 'main') ) batch_tasks.append(task) try: batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True) results.extend(batch_results) except Exception as error: print(f'Batch update error: {error}') # Small delay to avoid rate limiting if i + batch_size < len(inventory_updates): await asyncio.sleep(0.1) return results # Usage async def main(): inventory = InventoryManager(os.getenv('QUICKBUTIK_API_KEY')) # Update single product await inventory.update_product_stock('SHIRT-123', 45) # Bulk update await inventory.bulk_update_inventory([ {'sku': 'SHIRT-123', 'quantity': 45, 'location': 'warehouse-1'}, {'sku': 'PANTS-456', 'quantity': 12, 'location': 'warehouse-1'}, {'sku': 'HAT-789', 'quantity': 0, 'location': 'warehouse-2'} ]) asyncio.run(main()) ``` ## Webhook-Based Inventory Sync Set up real-time inventory synchronization using webhooks: ```javascript Express.js Webhook Handler theme={null} app.post('/webhooks/quickbutik', async (req, res) => { const { event_type, product_id } = req.query; // Acknowledge webhook immediately res.status(200).send('OK'); if (event_type === 'product.update') { await handleProductUpdate(product_id); } }); async function handleProductUpdate(productId) { try { // Fetch updated product details const products = await api.getProducts({ product_id: productId, include_details: true }); if (!products || products.length === 0) { throw new Error(`Product ${productId} not found`); } const product = products[0]; // Check if stock changed if (await hasStockChanged(product)) { await syncInventoryToExternalSystem(product); } } catch (error) { console.error(`Failed to handle product update for ${productId}:`, error); } } async function hasStockChanged(product) { // Compare with your local inventory database const localInventory = await getLocalInventory(product.sku); return localInventory.quantity !== parseInt(product.qty || 0); } async function syncInventoryToExternalSystem(product) { try { const inventoryUpdate = { sku: product.sku, quantity: parseInt(product.qty || 0), location: product.qty_location || 'default', updated_at: new Date().toISOString() }; const response = await fetch('https://your-system.com/api/inventory', { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.EXTERNAL_API_TOKEN}` }, body: JSON.stringify(inventoryUpdate) }); if (!response.ok) { throw new Error(`External API error: ${response.status}`); } console.log(`Synced inventory for ${product.sku}: ${product.qty} units`); } catch (error) { console.error(`Failed to sync inventory for ${product.sku}:`, error); // Add to retry queue await addToRetryQueue({ type: 'inventory_sync', product, timestamp: new Date() }); } } ``` ```python Flask Webhook Handler theme={null} @app.route('/webhooks/quickbutik', methods=['POST']) def quickbutik_webhook(): event_type = request.args.get('event_type') product_id = request.args.get('product_id') # Acknowledge webhook immediately response = jsonify({'status': 'received'}) if event_type == 'product.update': asyncio.create_task(handle_product_update(product_id)) return response, 200 async def handle_product_update(product_id): try: # Fetch updated product details products = await api.get_products( product_id=product_id, include_details=True ) if not products: raise Exception(f'Product {product_id} not found') product = products[0] # Check if stock changed if await has_stock_changed(product): await sync_inventory_to_external_system(product) except Exception as error: app.logger.error(f'Failed to handle product update for {product_id}: {error}') async def has_stock_changed(product): # Compare with your local inventory database local_inventory = await get_local_inventory(product['sku']) return local_inventory['quantity'] != int(product.get('qty', 0)) async def sync_inventory_to_external_system(product): try: inventory_update = { 'sku': product['sku'], 'quantity': int(product.get('qty', 0)), 'location': product.get('qty_location', 'default'), 'updated_at': datetime.now().isoformat() } async with aiohttp.ClientSession() as session: async with session.put( 'https://your-system.com/api/inventory', headers={ 'Content-Type': 'application/json', 'Authorization': f'Bearer {os.getenv("EXTERNAL_API_TOKEN")}' }, json=inventory_update ) as response: if not response.ok: raise Exception(f'External API error: {response.status}') app.logger.info(f'Synced inventory for {product["sku"]}: {product.get("qty", 0)} units') except Exception as error: app.logger.error(f'Failed to sync inventory for {product["sku"]}: {error}') # Add to retry queue await add_to_retry_queue({ 'type': 'inventory_sync', 'product': product, 'timestamp': datetime.now() }) ``` ## Low Stock Monitoring Implement automated low stock alerts and reordering: ```javascript Low Stock Monitor theme={null} class LowStockMonitor { constructor(apiKey, thresholds = {}) { this.api = new QuickbutikAPI(apiKey); this.defaultThreshold = thresholds.default || 10; this.customThresholds = thresholds.custom || {}; } async checkLowStock() { try { const products = await this.api.getProducts({ include_details: true, limit: 500 }); const lowStockProducts = []; for (const product of products) { if (product.variants && product.variants.length > 0) { // Check variants for (const variant of product.variants) { if (this.isLowStock(variant)) { lowStockProducts.push({ ...variant, parent_sku: product.sku, parent_title: product.title }); } } } else { // Check main product if (this.isLowStock(product)) { lowStockProducts.push(product); } } } if (lowStockProducts.length > 0) { await this.handleLowStockAlert(lowStockProducts); } return lowStockProducts; } catch (error) { console.error('Failed to check low stock:', error); throw error; } } isLowStock(product) { const currentStock = parseInt(product.qty || 0); const threshold = this.customThresholds[product.sku] || this.defaultThreshold; return currentStock > 0 && currentStock <= threshold; } async handleLowStockAlert(lowStockProducts) { // Send alert notification await this.sendLowStockNotification(lowStockProducts); // Auto-reorder if configured const autoReorderProducts = lowStockProducts.filter(p => this.shouldAutoReorder(p) ); if (autoReorderProducts.length > 0) { await this.createReorderRequests(autoReorderProducts); } } async sendLowStockNotification(products) { const message = `🚨 Low Stock Alert!\n\n${products.map(p => `${p.title || p.parent_title} (${p.sku}): ${p.qty} units remaining` ).join('\n')}`; // Send to Slack if (process.env.SLACK_WEBHOOK_URL) { await fetch(process.env.SLACK_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: message }) }); } // Send email if (process.env.EMAIL_WEBHOOK_URL) { await fetch(process.env.EMAIL_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ to: process.env.INVENTORY_EMAIL, subject: 'Low Stock Alert', body: message }) }); } } shouldAutoReorder(product) { // Add your auto-reorder logic here return product.supplier_sku && product.auto_reorder_enabled; } async createReorderRequests(products) { for (const product of products) { try { const reorderQuantity = this.calculateReorderQuantity(product); await this.createPurchaseOrder({ sku: product.sku, supplier_sku: product.supplier_sku, quantity: reorderQuantity, supplier: product.supplier_name }); console.log(`Created reorder for ${product.sku}: ${reorderQuantity} units`); } catch (error) { console.error(`Failed to create reorder for ${product.sku}:`, error); } } } calculateReorderQuantity(product) { // Simple reorder logic - you can make this more sophisticated const currentStock = parseInt(product.qty || 0); const threshold = this.customThresholds[product.sku] || this.defaultThreshold; const targetStock = threshold * 3; // Reorder to 3x threshold return Math.max(targetStock - currentStock, threshold); } } // Usage const monitor = new LowStockMonitor(process.env.QUICKBUTIK_API_KEY, { default: 10, custom: { 'POPULAR-ITEM-123': 50, 'SEASONAL-456': 5 } }); // Run every hour setInterval(async () => { try { const lowStockProducts = await monitor.checkLowStock(); console.log(`Found ${lowStockProducts.length} low stock products`); } catch (error) { console.error('Low stock check failed:', error); } }, 60 * 60 * 1000); ``` ## Multi-Location Inventory Handle inventory across multiple locations: ```javascript Multi-Location Manager theme={null} class MultiLocationInventory { constructor(apiKey) { this.api = new QuickbutikAPI(apiKey); this.locations = { 'warehouse-1': { name: 'Main Warehouse', priority: 1 }, 'warehouse-2': { name: 'Secondary Warehouse', priority: 2 }, 'store-front': { name: 'Physical Store', priority: 3 } }; } async getInventoryByLocation(sku) { try { const products = await this.api.getProducts({ product_id: sku, include_details: true }); if (!products || products.length === 0) { return null; } const product = products[0]; const inventory = {}; // If product has variants, aggregate by location if (product.variants && product.variants.length > 0) { for (const variant of product.variants) { const location = variant.qty_location || 'default'; const quantity = parseInt(variant.qty || 0); inventory[location] = (inventory[location] || 0) + quantity; } } else { const location = product.qty_location || 'default'; inventory[location] = parseInt(product.qty || 0); } return { sku: product.sku, title: product.title, total_quantity: Object.values(inventory).reduce((sum, qty) => sum + qty, 0), locations: inventory }; } catch (error) { console.error(`Failed to get inventory for ${sku}:`, error); throw error; } } async transferStock(sku, fromLocation, toLocation, quantity) { try { // Get current inventory const inventory = await this.getInventoryByLocation(sku); if (!inventory || !inventory.locations[fromLocation]) { throw new Error(`No inventory found for ${sku} at ${fromLocation}`); } if (inventory.locations[fromLocation] < quantity) { throw new Error(`Insufficient stock at ${fromLocation}. Available: ${inventory.locations[fromLocation]}, Requested: ${quantity}`); } // Update source location (decrease) await this.updateLocationStock(sku, fromLocation, inventory.locations[fromLocation] - quantity); // Update destination location (increase) const currentDestStock = inventory.locations[toLocation] || 0; await this.updateLocationStock(sku, toLocation, currentDestStock + quantity); console.log(`Transferred ${quantity} units of ${sku} from ${fromLocation} to ${toLocation}`); return { sku, quantity, from: fromLocation, to: toLocation, timestamp: new Date().toISOString() }; } catch (error) { console.error(`Failed to transfer stock for ${sku}:`, error); throw error; } } async updateLocationStock(sku, location, newQuantity) { return this.api.updateProducts({ sku, stock: newQuantity, qty_location: location }); } async getOptimalFulfillmentLocation(sku, requestedQuantity) { const inventory = await this.getInventoryByLocation(sku); if (!inventory) { return null; } // Sort locations by priority and availability const availableLocations = Object.entries(inventory.locations) .filter(([location, quantity]) => quantity >= requestedQuantity) .map(([location, quantity]) => ({ location, quantity, priority: this.locations[location]?.priority || 999 })) .sort((a, b) => a.priority - b.priority); return availableLocations.length > 0 ? availableLocations[0] : null; } } // Usage const multiLocation = new MultiLocationInventory(process.env.QUICKBUTIK_API_KEY); // Get inventory across all locations const inventory = await multiLocation.getInventoryByLocation('SHIRT-123'); console.log('Inventory:', inventory); // Transfer stock between locations await multiLocation.transferStock('SHIRT-123', 'warehouse-1', 'store-front', 5); // Find optimal fulfillment location const optimal = await multiLocation.getOptimalFulfillmentLocation('SHIRT-123', 10); console.log('Optimal location:', optimal); ``` ## Preventing Overselling Implement safeguards to prevent overselling: ```javascript Overselling Prevention theme={null} class OversellProtection { constructor(apiKey) { this.api = new QuickbutikAPI(apiKey); this.safetyBuffer = 1; // Keep 1 unit as safety buffer this.checkInterval = 5 * 60 * 1000; // Check every 5 minutes } async enableOversellProtection() { // Start monitoring setInterval(() => { this.checkAndProtectInventory(); }, this.checkInterval); console.log('Oversell protection enabled'); } async checkAndProtectInventory() { try { const products = await this.api.getProducts({ include_details: true, limit: 500 }); const protectionActions = []; for (const product of products) { // Skip if minus quantity is already disabled if (product.disable_minusqty === '1') { continue; } const currentStock = parseInt(product.qty || 0); if (currentStock <= this.safetyBuffer) { protectionActions.push({ sku: product.sku, title: product.title, currentStock, action: 'disable_minus_qty' }); await this.protectProduct(product.sku); } } if (protectionActions.length > 0) { await this.notifyProtectionActions(protectionActions); } } catch (error) { console.error('Failed to check oversell protection:', error); } } async protectProduct(sku) { try { await this.api.updateProducts({ sku, disable_minusqty: '1' }); console.log(`Enabled oversell protection for ${sku}`); } catch (error) { console.error(`Failed to protect product ${sku}:`, error); } } async unprotectProduct(sku) { try { await this.api.updateProducts({ sku, disable_minusqty: '0' }); console.log(`Disabled oversell protection for ${sku}`); } catch (error) { console.error(`Failed to unprotect product ${sku}:`, error); } } async notifyProtectionActions(actions) { const message = `πŸ›‘οΈ Oversell Protection Activated!\n\n${actions.map(action => `${action.title} (${action.sku}): ${action.currentStock} units remaining - Protection enabled` ).join('\n')}`; // Send notification if (process.env.SLACK_WEBHOOK_URL) { await fetch(process.env.SLACK_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: message }) }); } } async manualStockCheck(sku) { try { const products = await this.api.getProducts({ product_id: sku, include_details: true }); if (!products || products.length === 0) { throw new Error(`Product ${sku} not found`); } const product = products[0]; const currentStock = parseInt(product.qty || 0); const isProtected = product.disable_minusqty === '1'; return { sku: product.sku, title: product.title, currentStock, isProtected, recommendedAction: this.getRecommendedAction(currentStock, isProtected) }; } catch (error) { console.error(`Failed manual stock check for ${sku}:`, error); throw error; } } getRecommendedAction(currentStock, isProtected) { if (currentStock <= this.safetyBuffer && !isProtected) { return 'ENABLE_PROTECTION'; } else if (currentStock > this.safetyBuffer && isProtected) { return 'DISABLE_PROTECTION'; } else { return 'NO_ACTION_NEEDED'; } } } // Usage const protection = new OversellProtection(process.env.QUICKBUTIK_API_KEY); // Enable automatic protection await protection.enableOversellProtection(); // Manual check for specific product const status = await protection.manualStockCheck('SHIRT-123'); console.log('Stock status:', status); if (status.recommendedAction === 'ENABLE_PROTECTION') { await protection.protectProduct(status.sku); } else if (status.recommendedAction === 'DISABLE_PROTECTION') { await protection.unprotectProduct(status.sku); } ``` ## 🎯 Best Practices **Process inventory updates in batches** to avoid rate limiting and improve performance. **Implement retry logic** for failed inventory updates with exponential backoff. **Validate inventory data** before updates to prevent incorrect stock levels. **Monitor inventory sync health** with metrics and alerts for critical failures. ## Next Steps Learn how to sync complete product catalogs between systems # Order Synchronization Tutorial Source: https://quickbutik.dev/api-v1/guides/order-sync-tutorial Build a complete order sync system with webhooks and automatic retry logic This tutorial shows you how to build a robust order synchronization system that automatically syncs orders from Quickbutik to your external system using webhooks and the API. **What you'll learn:** * Set up webhook endpoints to receive order notifications * Implement automatic retry logic for failed API calls * Handle order status updates bidirectionally * Build a production-ready integration with proper error handling ## Architecture Overview ```mermaid theme={null} graph LR A[Quickbutik Store] -->|Webhook| B[Your Webhook Endpoint] B --> C[Order Processing Service] C --> D[External System] C -->|Status Updates| E[Quickbutik API] C --> F[Database/Queue] ``` ## Part 1: Setting Up Webhook Endpoint First, let's create a webhook endpoint to receive order notifications: ```javascript Express.js theme={null} const express = require('express'); const crypto = require('crypto'); const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Webhook endpoint for order notifications app.post('/webhooks/quickbutik', async (req, res) => { try { const { event_type, order_id } = req.query; console.log(`Received webhook: ${event_type} for order ${order_id}`); // Acknowledge webhook immediately res.status(200).send('OK'); // Process webhook asynchronously await processOrderWebhook(event_type, order_id); } catch (error) { console.error('Webhook processing error:', error); res.status(500).send('Error processing webhook'); } }); async function processOrderWebhook(eventType, orderId) { switch (eventType) { case 'order.new': await handleNewOrder(orderId); break; case 'order.done': await handleOrderShipped(orderId); break; case 'order.cancelled': await handleOrderCancelled(orderId); break; default: console.log(`Unhandled event type: ${eventType}`); } } app.listen(3000, () => { console.log('Webhook server running on port 3000'); }); ``` ```python Flask theme={null} from flask import Flask, request, jsonify import asyncio import logging app = Flask(__name__) logging.basicConfig(level=logging.INFO) @app.route('/webhooks/quickbutik', methods=['POST']) def quickbutik_webhook(): try: event_type = request.args.get('event_type') order_id = request.args.get('order_id') app.logger.info(f'Received webhook: {event_type} for order {order_id}') # Acknowledge webhook immediately response = jsonify({'status': 'received'}) # Process webhook asynchronously (in production, use a task queue) asyncio.create_task(process_order_webhook(event_type, order_id)) return response, 200 except Exception as error: app.logger.error(f'Webhook processing error: {error}') return jsonify({'error': 'Error processing webhook'}), 500 async def process_order_webhook(event_type, order_id): try: if event_type == 'order.new': await handle_new_order(order_id) elif event_type == 'order.done': await handle_order_shipped(order_id) elif event_type == 'order.cancelled': await handle_order_cancelled(order_id) else: app.logger.info(f'Unhandled event type: {event_type}') except Exception as error: app.logger.error(f'Error processing webhook: {error}') if __name__ == '__main__': app.run(debug=True, port=3000) ``` **Important:** Always acknowledge webhooks quickly (within 10 seconds) to avoid retries. Process the actual work asynchronously. ## Part 2: Fetching Order Details When you receive a webhook, fetch the complete order details: ```javascript Node.js theme={null} const QuickbutikAPI = require('./quickbutik-api'); // From our previous tutorial class OrderSyncService { constructor(apiKey) { this.api = new QuickbutikAPI(apiKey); this.retryAttempts = 3; this.retryDelay = 1000; // 1 second } async handleNewOrder(orderId) { try { // Fetch complete order details const orders = await this.retryAPICall(() => this.api.getOrders({ order_id: orderId, include_details: true, apps_load: true }) ); if (!orders || orders.length === 0) { throw new Error(`Order ${orderId} not found`); } const order = orders[0]; // Validate order data if (!this.validateOrder(order)) { throw new Error(`Invalid order data for order ${orderId}`); } // Sync to external system await this.syncOrderToExternalSystem(order); // Log success console.log(`Successfully synced order ${orderId} to external system`); } catch (error) { console.error(`Failed to process new order ${orderId}:`, error); // Add to retry queue or send alert await this.handleOrderSyncFailure(orderId, error); } } async retryAPICall(apiCall, attempt = 1) { try { return await apiCall(); } catch (error) { if (attempt < this.retryAttempts) { console.log(`API call failed, retrying in ${this.retryDelay}ms (attempt ${attempt}/${this.retryAttempts})`); await new Promise(resolve => setTimeout(resolve, this.retryDelay * attempt)); return this.retryAPICall(apiCall, attempt + 1); } throw error; } } validateOrder(order) { return order.order_id && order.total_amount && order.customer && order.customer.email; } async syncOrderToExternalSystem(order) { // Transform Quickbutik order to your system's format const externalOrder = this.transformOrder(order); // Make API call to your external system const response = await fetch('https://your-system.com/api/orders', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.EXTERNAL_API_TOKEN}` }, body: JSON.stringify(externalOrder) }); if (!response.ok) { throw new Error(`External API error: ${response.status} ${response.statusText}`); } return response.json(); } transformOrder(quickbutikOrder) { return { external_order_id: quickbutikOrder.order_id, customer_email: quickbutikOrder.customer.email, total_amount: parseFloat(quickbutikOrder.total_amount), currency: quickbutikOrder.payment?.currency || 'SEK', items: quickbutikOrder.products?.map(product => ({ sku: product.sku, quantity: product.qty || 1, price: parseFloat(product.price || 0) })) || [], shipping_address: quickbutikOrder.customer.shipping_details, billing_address: quickbutikOrder.customer.billing_details, created_at: quickbutikOrder.date_created }; } async handleOrderSyncFailure(orderId, error) { // In production, you might: // 1. Add to a retry queue (Redis, SQS, etc.) // 2. Send alerts to monitoring system // 3. Log to error tracking service console.error(`Order sync failed for ${orderId}:`, error.message); // Example: Add to retry queue // await this.addToRetryQueue({ orderId, error: error.message, timestamp: new Date() }); } } // Usage const orderSync = new OrderSyncService(process.env.QUICKBUTIK_API_KEY); async function handleNewOrder(orderId) { await orderSync.handleNewOrder(orderId); } ``` ```python Python theme={null} import asyncio import aiohttp import json from typing import Dict, List, Optional from quickbutik_api import QuickbutikAPI # From previous tutorial class OrderSyncService: def __init__(self, api_key: str): self.api = QuickbutikAPI(api_key) self.retry_attempts = 3 self.retry_delay = 1 # seconds async def handle_new_order(self, order_id: str): try: # Fetch complete order details orders = await self.retry_api_call( lambda: self.api.get_orders( order_id=order_id, include_details=True, apps_load=True ) ) if not orders: raise Exception(f'Order {order_id} not found') order = orders[0] # Validate order data if not self.validate_order(order): raise Exception(f'Invalid order data for order {order_id}') # Sync to external system await self.sync_order_to_external_system(order) print(f'Successfully synced order {order_id} to external system') except Exception as error: print(f'Failed to process new order {order_id}: {error}') await self.handle_order_sync_failure(order_id, error) async def retry_api_call(self, api_call, attempt: int = 1): try: return await api_call() except Exception as error: if attempt < self.retry_attempts: print(f'API call failed, retrying in {self.retry_delay * attempt}s (attempt {attempt}/{self.retry_attempts})') await asyncio.sleep(self.retry_delay * attempt) return await self.retry_api_call(api_call, attempt + 1) raise error def validate_order(self, order: Dict) -> bool: return (order.get('order_id') and order.get('total_amount') and order.get('customer') and order.get('customer', {}).get('email')) async def sync_order_to_external_system(self, order: Dict): # Transform Quickbutik order to your system's format external_order = self.transform_order(order) # Make API call to your external system async with aiohttp.ClientSession() as session: async with session.post( 'https://your-system.com/api/orders', headers={ 'Content-Type': 'application/json', 'Authorization': f'Bearer {os.getenv("EXTERNAL_API_TOKEN")}' }, json=external_order ) as response: if not response.ok: raise Exception(f'External API error: {response.status} {await response.text()}') return await response.json() def transform_order(self, quickbutik_order: Dict) -> Dict: return { 'external_order_id': quickbutik_order['order_id'], 'customer_email': quickbutik_order['customer']['email'], 'total_amount': float(quickbutik_order['total_amount']), 'currency': quickbutik_order.get('payment', {}).get('currency', 'SEK'), 'items': [ { 'sku': product.get('sku'), 'quantity': product.get('qty', 1), 'price': float(product.get('price', 0)) } for product in quickbutik_order.get('products', []) ], 'shipping_address': quickbutik_order['customer'].get('shipping_details'), 'billing_address': quickbutik_order['customer'].get('billing_details'), 'created_at': quickbutik_order.get('date_created') } async def handle_order_sync_failure(self, order_id: str, error: Exception): # In production, you might: # 1. Add to a retry queue (Redis, Celery, etc.) # 2. Send alerts to monitoring system # 3. Log to error tracking service print(f'Order sync failed for {order_id}: {error}') # Example: Add to retry queue # await self.add_to_retry_queue({'order_id': order_id, 'error': str(error), 'timestamp': datetime.now()}) # Usage order_sync = OrderSyncService(os.getenv('QUICKBUTIK_API_KEY')) async def handle_new_order(order_id: str): await order_sync.handle_new_order(order_id) ``` ## Part 3: Bidirectional Status Updates Update Quickbutik when orders are processed in your external system: ```javascript Node.js theme={null} class OrderStatusManager { constructor(apiKey) { this.api = new QuickbutikAPI(apiKey); } async markOrderAsShipped(orderId, shippingInfo) { try { const statusUpdate = { order_id: orderId, status: 'done', shipping_info: { trackingnumber: shippingInfo.trackingNumber, company: shippingInfo.carrier }, email_confirmation: 'true' }; const result = await this.api.updateOrderStatus(statusUpdate); console.log(`Order ${orderId} marked as shipped with tracking ${shippingInfo.trackingNumber}`); return result; } catch (error) { console.error(`Failed to update order ${orderId} status:`, error); throw error; } } async markOrderAsPaid(orderId, paymentInfo = {}) { try { const statusUpdate = { order_id: orderId, status: 'paid', skip_email_confirmation: paymentInfo.skipEmail ? 'true' : 'false' }; const result = await this.api.updateOrderStatus(statusUpdate); console.log(`Order ${orderId} marked as paid`); return result; } catch (error) { console.error(`Failed to mark order ${orderId} as paid:`, error); throw error; } } async cancelOrder(orderId, reason = '') { try { const statusUpdate = { order_id: orderId, status: 'cancelled' }; const result = await this.api.updateOrderStatus(statusUpdate); console.log(`Order ${orderId} cancelled. Reason: ${reason}`); return result; } catch (error) { console.error(`Failed to cancel order ${orderId}:`, error); throw error; } } } // Usage example - webhook from your external system app.post('/webhooks/external-system', async (req, res) => { const { event_type, order_id, data } = req.body; const statusManager = new OrderStatusManager(process.env.QUICKBUTIK_API_KEY); try { switch (event_type) { case 'order.shipped': await statusManager.markOrderAsShipped(order_id, { trackingNumber: data.tracking_number, carrier: data.carrier }); break; case 'order.payment_confirmed': await statusManager.markOrderAsPaid(order_id); break; case 'order.cancelled': await statusManager.cancelOrder(order_id, data.reason); break; } res.status(200).json({ status: 'success' }); } catch (error) { res.status(500).json({ error: error.message }); } }); ``` ```python Python theme={null} class OrderStatusManager: def __init__(self, api_key: str): self.api = QuickbutikAPI(api_key) async def mark_order_as_shipped(self, order_id: str, shipping_info: Dict): try: status_update = { 'order_id': order_id, 'status': 'done', 'shipping_info': { 'trackingnumber': shipping_info['tracking_number'], 'company': shipping_info['carrier'] }, 'email_confirmation': 'true' } result = await self.api.update_order_status(status_update) print(f'Order {order_id} marked as shipped with tracking {shipping_info["tracking_number"]}') return result except Exception as error: print(f'Failed to update order {order_id} status: {error}') raise error async def mark_order_as_paid(self, order_id: str, payment_info: Dict = {}): try: status_update = { 'order_id': order_id, 'status': 'paid', 'skip_email_confirmation': 'true' if payment_info.get('skip_email') else 'false' } result = await self.api.update_order_status(status_update) print(f'Order {order_id} marked as paid') return result except Exception as error: print(f'Failed to mark order {order_id} as paid: {error}') raise error async def cancel_order(self, order_id: str, reason: str = ''): try: status_update = { 'order_id': order_id, 'status': 'cancelled' } result = await self.api.update_order_status(status_update) print(f'Order {order_id} cancelled. Reason: {reason}') return result except Exception as error: print(f'Failed to cancel order {order_id}: {error}') raise error # Usage example - webhook from your external system @app.route('/webhooks/external-system', methods=['POST']) async def external_system_webhook(): data = request.get_json() event_type = data.get('event_type') order_id = data.get('order_id') event_data = data.get('data', {}) status_manager = OrderStatusManager(os.getenv('QUICKBUTIK_API_KEY')) try: if event_type == 'order.shipped': await status_manager.mark_order_as_shipped(order_id, { 'tracking_number': event_data['tracking_number'], 'carrier': event_data['carrier'] }) elif event_type == 'order.payment_confirmed': await status_manager.mark_order_as_paid(order_id) elif event_type == 'order.cancelled': await status_manager.cancel_order(order_id, event_data.get('reason', '')) return jsonify({'status': 'success'}), 200 except Exception as error: return jsonify({'error': str(error)}), 500 ``` ## Part 4: Production Deployment Here's a production-ready setup with Docker: ```dockerfile Dockerfile theme={null} FROM node:18-alpine WORKDIR /app # Copy package files COPY package*.json ./ RUN npm ci --only=production # Copy application code COPY . . # Create non-root user RUN addgroup -g 1001 -S nodejs RUN adduser -S nodejs -u 1001 # Change ownership and switch to non-root user RUN chown -R nodejs:nodejs /app USER nodejs EXPOSE 3000 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:3000/health || exit 1 CMD ["node", "server.js"] ``` ```yaml docker-compose.yml theme={null} version: '3.8' services: order-sync: build: . ports: - "3000:3000" environment: - QUICKBUTIK_API_KEY=${QUICKBUTIK_API_KEY} - EXTERNAL_API_TOKEN=${EXTERNAL_API_TOKEN} - REDIS_URL=${REDIS_URL} - NODE_ENV=production depends_on: - redis restart: unless-stopped redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis_data:/data restart: unless-stopped nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./ssl:/etc/nginx/ssl depends_on: - order-sync restart: unless-stopped volumes: redis_data: ``` ## Part 5: Monitoring and Alerting Add comprehensive monitoring to your integration: ```javascript Monitoring theme={null} const prometheus = require('prom-client'); // Create metrics const webhookCounter = new prometheus.Counter({ name: 'quickbutik_webhooks_total', help: 'Total number of webhooks received', labelNames: ['event_type', 'status'] }); const orderSyncDuration = new prometheus.Histogram({ name: 'order_sync_duration_seconds', help: 'Time spent syncing orders', buckets: [0.1, 0.5, 1, 2, 5, 10] }); const orderSyncErrors = new prometheus.Counter({ name: 'order_sync_errors_total', help: 'Total number of order sync errors', labelNames: ['error_type'] }); // Add metrics to webhook handler app.post('/webhooks/quickbutik', async (req, res) => { const { event_type, order_id } = req.query; const timer = orderSyncDuration.startTimer(); try { webhookCounter.inc({ event_type, status: 'received' }); res.status(200).send('OK'); await processOrderWebhook(event_type, order_id); webhookCounter.inc({ event_type, status: 'processed' }); timer(); } catch (error) { orderSyncErrors.inc({ error_type: error.constructor.name }); webhookCounter.inc({ event_type, status: 'error' }); timer(); // Send alert await sendAlert({ type: 'order_sync_error', orderId, error: error.message, timestamp: new Date() }); } }); // Metrics endpoint app.get('/metrics', async (req, res) => { res.set('Content-Type', prometheus.register.contentType); res.end(await prometheus.register.metrics()); }); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime() }); }); async function sendAlert(alert) { // Send to Slack, PagerDuty, email, etc. console.error('ALERT:', alert); // Example: Slack webhook if (process.env.SLACK_WEBHOOK_URL) { await fetch(process.env.SLACK_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: `🚨 Order sync error for order ${alert.orderId}: ${alert.error}` }) }); } } ``` ## πŸŽ‰ You Did It! You now have a production-ready order synchronization system with: * βœ… Webhook handling with proper acknowledgment * βœ… Automatic retry logic for failed API calls * βœ… Bidirectional order status updates * βœ… Comprehensive error handling and monitoring * βœ… Production deployment configuration ## Next Steps Learn how to keep product stock levels synchronized # Introduction Source: https://quickbutik.dev/api-v1/introduction Everything you need to start integrating with the Quickbutik API Welcome to the **Quickbutik API**! Our REST API allows you to manage your store's orders, products, categories, and more programmatically. Build powerful integrations to automate your e-commerce operations. ## Base URL All API requests are made to our base URL: ``` https://api.quickbutik.com/v1 ``` **OpenAPI Specification:** [https://quickbutik.dev/openapi.yaml](https://quickbutik.dev/openapi.yaml) ## Key Features Create, update, and retrieve orders with full order lifecycle support Manage your product inventory, pricing, and product information Organize products with hierarchical category structures Get instant updates via webhooks when important events occur ## Getting Started Generate an API key in the Quickbutik Control Panel under **Settings β†’ API** All requests require [Basic Authentication](/api-v1/authentication) using your API key Try fetching your product count to test your setup: ```bash theme={null} curl https://api.quickbutik.com/v1/products/count \ -u your_api_key:your_api_key ``` ## Response Format All API responses are returned in JSON format, including error messages. Successful responses will contain the requested data, while errors will include helpful error codes and descriptions. ### Success Response Example ```json theme={null} { "count": "42" } ``` ### Error Response Example ```json theme={null} { "code": 404, "error": "Resource not found" } ``` ## HTTP Status Codes The API uses conventional HTTP response codes: * `200` - OK: The request was successful * `400` - Bad Request: The request was invalid * `401` - Unauthorized: Authentication failed * `404` - Not Found: The requested resource doesn't exist * `500` - Internal Server Error: Something went wrong on our end ## Rate Limiting To ensure fair usage and optimal performance for all users, please be mindful of your request frequency. If you encounter rate limiting, implement exponential backoff in your retry logic. ## Support We're here to help! For feature requests, feedback, or technical questions: Email us at [support@quickbutik.com](mailto:support@quickbutik.com) We greatly appreciate your feedback and are always working to improve our API. Ready to get started? Check out our [Authentication guide](/api-v1/authentication) to begin integrating with the Quickbutik API. # Create metadata Source: https://quickbutik.dev/api-v1/metadata/create-metadata post /v1/metadata/{scope}/{id} Add metadata to selected scope # Get metadata Source: https://quickbutik.dev/api-v1/metadata/get-metadata get /v1/metadata/{scope}/{id} Get metadata for selected scope and id # Update metadata Source: https://quickbutik.dev/api-v1/metadata/update-metadata put /v1/metadata/{scope}/{id} Update metadata to selected scope. Delete when empty value # Create orders Source: https://quickbutik.dev/api-v1/orders/create-orders post /v1/orders Create new order and add order content. Status for new orders will always be set to **unpaid** # Get orders Source: https://quickbutik.dev/api-v1/orders/get-orders get /v1/orders Retrieve a list of orders with optional filtering, pagination and sorting. # Update orders Source: https://quickbutik.dev/api-v1/orders/update-orders put /v1/orders Update one or more existing orders. Each request object must include the `order_id` of the order to update, plus any fields you want to change # Get payment methods Source: https://quickbutik.dev/api-v1/payment-methods/get-payment-methods get /v1/paymentmethods Fetch store payment methods # Count products Source: https://quickbutik.dev/api-v1/products/count-products get /v1/products/count Fetches a count of the total number of products # Create products Source: https://quickbutik.dev/api-v1/products/create-products post /v1/products Create and add products to store # Delete products Source: https://quickbutik.dev/api-v1/products/delete-products delete /v1/products Deletes a product from the store entirely. **NOTE:** This is non-reversible # Get products Source: https://quickbutik.dev/api-v1/products/get-products get /v1/products Fetch products in store with optional filtering. When no specific product_id or sku is provided, the following filters can be used to search and filter products. **Important - Response Structure Varies:** - When using the `search` parameter: Returns a direct array `[...]` - When NOT using the `search` parameter: Returns `{product: [...]}` - When requesting a specific product by `product_id` or `sku`: Returns `{product: {...}}` # Update products Source: https://quickbutik.dev/api-v1/products/update-products put /v1/products Update products in store. Product can be identified by product_id/variant_id or directly with SKU/Article Number if unique # Quickstart Tutorial Source: https://quickbutik.dev/api-v1/quickstart-tutorial Build your first Quickbutik integration in 15 minutes This tutorial will walk you through building your first Quickbutik integration. You'll learn how to authenticate, fetch data, and handle responses. **Prerequisites**: You'll need a Quickbutik store and an API key. If you don't have an API key yet, generate one in your store's Control Panel under **Settings β†’ API**. ## What we'll build By the end of this tutorial, you'll have a simple integration that: * βœ… Authenticates with the Quickbutik API * βœ… Fetches your store's product count * βœ… Retrieves your latest orders * βœ… Handles errors gracefully ## Step 1: Set up authentication First, let's test your API connection with a simple request to count your products: ```bash cURL theme={null} # Replace 'your_api_key' with your actual API key curl https://api.quickbutik.com/v1/products/count \ -u your_api_key:your_api_key \ -H "Content-Type: application/json" ``` ```javascript Node.js theme={null} const fetch = require('node-fetch'); // or use built-in fetch in Node 18+ const apiKey = 'your_api_key'; const credentials = Buffer.from(`${apiKey}:${apiKey}`).toString('base64'); async function getProductCount() { try { const response = await fetch('https://api.quickbutik.com/v1/products/count', { headers: { 'Authorization': `Basic ${credentials}`, 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); console.log('Product count:', data.count); return data; } catch (error) { console.error('Error:', error.message); } } getProductCount(); ``` ```python Python theme={null} import requests import base64 import json api_key = 'your_api_key' credentials = base64.b64encode(f'{api_key}:{api_key}'.encode()).decode() def get_product_count(): try: response = requests.get( 'https://api.quickbutik.com/v1/products/count', headers={ 'Authorization': f'Basic {credentials}', 'Content-Type': 'application/json' } ) response.raise_for_status() data = response.json() print(f"Product count: {data['count']}") return data except requests.exceptions.RequestException as error: print(f"Error: {error}") get_product_count() ``` ### Expected Response ```json theme={null} { "count": "42" } ``` **Common issues:** * **401 Unauthorized**: Check that your API key is correct * **Invalid base64**: Ensure you're encoding `api_key:api_key` format * **SSL errors**: Make sure you're using `https://` not `http://` **Test your base64 encoding:** ```bash theme={null} echo -n "your_api_key:your_api_key" | base64 ``` ## Step 2: Fetch your latest orders Now let's retrieve your most recent orders: ```bash cURL theme={null} curl "https://api.quickbutik.com/v1/orders?limit=5&include_details=true" \ -u your_api_key:your_api_key \ -H "Content-Type: application/json" ``` ```javascript Node.js theme={null} async function getLatestOrders() { try { const response = await fetch('https://api.quickbutik.com/v1/orders?limit=5&include_details=true', { headers: { 'Authorization': `Basic ${credentials}`, 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const orders = await response.json(); console.log(`Found ${orders.length} recent orders`); orders.forEach(order => { console.log(`Order #${order.order_id}: ${order.total_amount} (${order.status})`); }); return orders; } catch (error) { console.error('Error fetching orders:', error.message); } } getLatestOrders(); ``` ```python Python theme={null} def get_latest_orders(): try: response = requests.get( 'https://api.quickbutik.com/v1/orders', params={'limit': 5, 'include_details': True}, headers={ 'Authorization': f'Basic {credentials}', 'Content-Type': 'application/json' } ) response.raise_for_status() orders = response.json() print(f"Found {len(orders)} recent orders") for order in orders: print(f"Order #{order['order_id']}: {order['total_amount']} ({order['status']})") return orders except requests.exceptions.RequestException as error: print(f"Error fetching orders: {error}") get_latest_orders() ``` ### Expected Response ```json theme={null} [ { "order_id": "12345", "date_created": "2025-01-29 11:35:39", "total_amount": "148.95", "status": "1" } ] ``` ## Step 3: Update an order status Let's mark an order as "done" (shipped): ```bash cURL theme={null} curl -X PUT https://api.quickbutik.com/v1/orders \ -u your_api_key:your_api_key \ -H "Content-Type: application/json" \ -d '[{ "order_id": "12345", "status": "done", "shipping_info": { "trackingnumber": "1Z999AA1234567890", "company": "UPS" }, "email_confirmation": "true" }]' ``` ```javascript Node.js theme={null} async function markOrderAsShipped(orderId, trackingNumber) { try { const response = await fetch('https://api.quickbutik.com/v1/orders', { method: 'PUT', headers: { 'Authorization': `Basic ${credentials}`, 'Content-Type': 'application/json' }, body: JSON.stringify([{ order_id: orderId, status: 'done', shipping_info: { trackingnumber: trackingNumber, company: 'UPS' }, email_confirmation: 'true' }]) }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const result = await response.json(); console.log('Order updated successfully:', result); return result; } catch (error) { console.error('Error updating order:', error.message); } } markOrderAsShipped('12345', '1Z999AA1234567890'); ``` ```python Python theme={null} def mark_order_as_shipped(order_id, tracking_number): try: response = requests.put( 'https://api.quickbutik.com/v1/orders', headers={ 'Authorization': f'Basic {credentials}', 'Content-Type': 'application/json' }, json=[{ 'order_id': order_id, 'status': 'done', 'shipping_info': { 'trackingnumber': tracking_number, 'company': 'UPS' }, 'email_confirmation': 'true' }] ) response.raise_for_status() result = response.json() print('Order updated successfully:', result) return result except requests.exceptions.RequestException as error: print(f"Error updating order: {error}") mark_order_as_shipped('12345', '1Z999AA1234567890') ``` ## Step 4: Handle errors like a pro Here's how to build robust error handling: ```javascript Node.js theme={null} class QuickbutikAPI { constructor(apiKey) { this.apiKey = apiKey; this.credentials = Buffer.from(`${apiKey}:${apiKey}`).toString('base64'); this.baseUrl = 'https://api.quickbutik.com/v1'; } async request(endpoint, options = {}) { const url = `${this.baseUrl}${endpoint}`; try { const response = await fetch(url, { ...options, headers: { 'Authorization': `Basic ${this.credentials}`, 'Content-Type': 'application/json', ...options.headers } }); // Handle different HTTP status codes if (!response.ok) { const errorData = await response.json().catch(() => ({})); switch (response.status) { case 401: throw new Error('Authentication failed. Check your API key.'); case 404: throw new Error(`Resource not found: ${errorData.error || 'Unknown error'}`); case 400: throw new Error(`Bad request: ${errorData.error || 'Invalid data provided'}`); case 500: throw new Error('Server error. Please try again later.'); default: throw new Error(`HTTP ${response.status}: ${errorData.error || response.statusText}`); } } return await response.json(); } catch (error) { if (error.name === 'TypeError' && error.message.includes('fetch')) { throw new Error('Network error. Check your internet connection.'); } throw error; } } async getOrders(params = {}) { const queryString = new URLSearchParams(params).toString(); const endpoint = queryString ? `/orders?${queryString}` : '/orders'; return this.request(endpoint); } async updateOrderStatus(updates) { return this.request('/orders', { method: 'PUT', body: JSON.stringify(Array.isArray(updates) ? updates : [updates]) }); } } // Usage const api = new QuickbutikAPI('your_api_key'); async function example() { try { const orders = await api.getOrders({ limit: 5 }); console.log('Orders fetched successfully:', orders.length); } catch (error) { console.error('Failed to fetch orders:', error.message); } } ``` ```python Python theme={null} import requests import base64 from typing import Dict, List, Optional class QuickbutikAPI: def __init__(self, api_key: str): self.api_key = api_key self.credentials = base64.b64encode(f'{api_key}:{api_key}'.encode()).decode() self.base_url = 'https://api.quickbutik.com/v1' def _request(self, endpoint: str, method: str = 'GET', data: Optional[Dict] = None): url = f'{self.base_url}{endpoint}' headers = { 'Authorization': f'Basic {self.credentials}', 'Content-Type': 'application/json' } try: response = requests.request(method, url, headers=headers, json=data) # Handle different HTTP status codes if response.status_code == 401: raise Exception('Authentication failed. Check your API key.') elif response.status_code == 404: error_data = response.json() if response.content else {} raise Exception(f"Resource not found: {error_data.get('error', 'Unknown error')}") elif response.status_code == 400: error_data = response.json() if response.content else {} raise Exception(f"Bad request: {error_data.get('error', 'Invalid data provided')}") elif response.status_code == 500: raise Exception('Server error. Please try again later.') response.raise_for_status() return response.json() except requests.exceptions.ConnectionError: raise Exception('Network error. Check your internet connection.') except requests.exceptions.Timeout: raise Exception('Request timed out. Please try again.') except requests.exceptions.RequestException as e: raise Exception(f'Request failed: {str(e)}') def get_orders(self, **params) -> List[Dict]: endpoint = '/orders' if params: query_string = '&'.join([f'{k}={v}' for k, v in params.items()]) endpoint += f'?{query_string}' return self._request(endpoint) def update_order_status(self, updates) -> Dict: if not isinstance(updates, list): updates = [updates] return self._request('/orders', method='PUT', data=updates) # Usage api = QuickbutikAPI('your_api_key') try: orders = api.get_orders(limit=5) print(f'Orders fetched successfully: {len(orders)}') except Exception as error: print(f'Failed to fetch orders: {error}') ``` ## πŸŽ‰ Congratulations! You've successfully built your first Quickbutik integration! You now know how to: * βœ… Authenticate with the API * βœ… Fetch orders and products * βœ… Update order statuses * βœ… Handle errors gracefully ## Next steps Get real-time notifications when orders are created or updated Learn how to build a complete order synchronization system ## Need help? Stuck on something? We're here to help! Reach out to [support@quickbutik.com](mailto:support@quickbutik.com) with your questions. # Create script Source: https://quickbutik.dev/api-v1/scripts/create-script /openapi.yaml post /v1/scripts Create a new storefront script # Delete script Source: https://quickbutik.dev/api-v1/scripts/delete-script /openapi.yaml delete /v1/scripts/{id} Delete a storefront script # Get scripts Source: https://quickbutik.dev/api-v1/scripts/get-scripts /openapi.yaml get /v1/scripts List all storefront scripts or get a specific script by ID # Update or create script Source: https://quickbutik.dev/api-v1/scripts/update-or-create-script /openapi.yaml put /v1/scripts/{id} Update an existing script or create one with a specific ID # Get preorder options Source: https://quickbutik.dev/api-v1/settings/get-preorder-options /openapi.yaml get /v1/settings/preorders Retrieve all preorder options configured for the store. These options can be assigned to products and variants to indicate pre-order availability and estimated delivery windows. **Required scope:** `products:read` or `read_only` **Note:** This endpoint is not available to storefront-only API keys. # Get preorders Source: https://quickbutik.dev/api-v1/settings/get-preorders get /v1/preorders # Get shipping methods Source: https://quickbutik.dev/api-v1/shipping-methods/get-shipping-methods get /v1/shippingmethods Fetch store shipping methods # Build with AI Source: https://quickbutik.dev/build-with-ai Use AI tools to accelerate your Quickbutik integration development with our OpenAPI specification ## Quickbutik API (OpenAPI Specification) **[https://quickbutik.dev/openapi.yaml](https://quickbutik.dev/openapi.yaml)** ## Supercharge your development with AI The Quickbutik API is fully documented with an OpenAPI 3.1 specification, making it easy to use with AI-powered development tools like Cursor, GitHub Copilot, and ChatGPT. Access our complete OpenAPI specification file ## What you can do with the OpenAPI spec Our OpenAPI specification provides a machine-readable description of the entire Quickbutik API, including: * **All endpoints** - Complete paths for orders, products, categories, and more * **Request schemas** - Exact structure of request bodies and parameters * **Response formats** - Expected response structures and status codes * **Authentication details** - How to authenticate your API requests * **Data types and validation** - Field types, constraints, and validation rules ## Using with AI coding assistants ### Cursor AI If you're using Cursor, you can reference the OpenAPI spec directly in your prompts: ``` @https://quickbutik.dev/openapi.yaml Create a function to fetch all orders from the last 30 days ``` ### GitHub Copilot & ChatGPT Share the OpenAPI specification with these tools to get context-aware code suggestions: 1. Download or reference the OpenAPI spec: `https://quickbutik.dev/openapi.yaml` 2. Paste relevant sections or the full spec into your conversation 3. Ask the AI to generate code based on the specification ## Example prompts Here are some example prompts you can use with AI assistants: "Using the Quickbutik OpenAPI spec at [https://quickbutik.dev/openapi.yaml](https://quickbutik.dev/openapi.yaml), generate a TypeScript client for fetching and updating products" "Based on the Quickbutik API spec, create Python functions to sync orders between my system and Quickbutik" "Using the Quickbutik OpenAPI specification, create a Node.js Express server that handles order webhook events" ## Next steps Explore the full API documentation Follow a step-by-step tutorial # Quickbutik Developers Source: https://quickbutik.dev/index Welcome! Here you'll find everything you need to create beautiful themes and powerful integrations for e-commerce stores. ## Getting started Where would you like to begin? Handle all your frontend needs with our templating system. Design layouts, customize functionality, and create responsive storefronts for your customers. Manage your backend operations with our API & Webhooks. Integrate products, orders, customers and more through our straightforward endpoints and webhooks. Use AI tools like Cursor, GitHub Copilot, and ChatGPT with our OpenAPI specification to accelerate your development. Direct access to our complete OpenAPI 3.1 specification for AI tools and API client generators. # Resources Source: https://quickbutik.dev/resources Essential resources, tools, and references for Quickbutik developers # Developer Resources Everything you need to accelerate your development with Quickbutik - from quick references to comprehensive guides. ## Popular Resources Quick access to the most commonly used documentation: Learn the templating language fundamentals and start building your first theme Get started with your first API call and understand authentication Access store settings, configuration, and global shop data in your themes Set up real-time event notifications for orders, products, and more ## Quick Links by Category ### Theme Development Access and display data in your themes Control flow and conditional rendering Image processing, translations, and utilities Shop, user, basket, and navigation data Product, category, order, and blog objects Performance, SEO, and development tips ### API Integration API keys, tokens, and security Manage product catalog and inventory Process and manage orders Set up real-time event notifications for orders, products, and more # Conditionals Source: https://quickbutik.dev/theme-development/conditionals Master advanced conditional logic, boolean operations, and dynamic content display in Mustache templates Conditionals in Mustache allow you to display content based on whether certain conditions are met. This guide covers advanced conditional patterns and techniques for building dynamic, responsive themes using only supported Quickbutik properties. **Prerequisites**: This guide assumes you understand basic Mustache syntax. Review [Mustache Basics](/theme-development/mustache-basics) first if you're new to Mustache conditionals. ## Advanced Conditional Logic ### Complex If-Else Patterns Combine positive and negative conditionals for sophisticated logic: ```mustache User Authentication States theme={null} {{#shop.login_active}} {{#user.logged_in}}
{{#lang}}Welcome back{{/lang}}
{{/user.logged_in}} {{^user.logged_in}}
{{#lang}}Login{{/lang}}

{{#lang}}Join for exclusive deals and faster checkout{{/lang}}

{{/user.logged_in}} {{/shop.login_active}} {{^shop.login_active}}

{{#lang}}Guest checkout available{{/lang}}

{{/shop.login_active}} ``` ```mustache Product Pricing Display theme={null} {{#product.has_before_price}}
{{product.before_price}} {{product.price}} {{#lang}}Sale{{/lang}}
{{/product.has_before_price}} {{^product.has_before_price}}
{{product.price}}
{{/product.has_before_price}} ```
### Deeply Nested Conditionals Handle complex business logic with multiple levels of conditions: ```mustache Product Availability Management theme={null} {{#product.soldOut}}
{{#shop.soldout_text}}

{{shop.soldout_text}}

{{/shop.soldout_text}} {{^shop.soldout_text}}

{{#lang}}Currently unavailable{{/lang}}

{{/shop.soldout_text}}
{{/product.soldOut}} {{^product.soldOut}}
{{#lang}}Available{{/lang}}
{{/product.soldOut}} ```
## Working with Arrays and Collections ### Advanced Array Conditionals Handle complex array states and conditions: ```mustache Smart Image Gallery theme={null} {{#product.images}} {{/product.images}} {{^product.images}}
{{#product.firstimage}} {{product.title}} {{/product.firstimage}} {{^product.firstimage}}
{{#lang}}No image available{{/lang}}
{{/product.firstimage}}
{{/product.images}} ``` ```mustache Advanced Product Grid theme={null} {{#products}}
{{#has_before_price}} {{#lang}}Sale{{/lang}} {{/has_before_price}}
{{#firstimage}} {{title}} {{/firstimage}} {{^firstimage}} {{#lang}}No image available{{/lang}} {{/firstimage}}

{{title}}

{{#has_before_price}} {{before_price}} {{price}} {{/has_before_price}} {{^has_before_price}} {{price}} {{/has_before_price}}
{{^soldOut}} {{#lang}}View Product{{/lang}} {{/soldOut}} {{#soldOut}} {{/soldOut}}
{{/products}} {{^products}}

{{#lang}}No products in this category yet{{/lang}}

{{#lang}}Check back soon for new arrivals{{/lang}}

{{#lang}}Browse All Products{{/lang}}
{{/products}} ```
## Conditional Classes and Attributes Use conditionals to dynamically apply CSS classes and HTML attributes: ```mustache Dynamic Navigation theme={null} {{#linklist.main}} {{/linklist.main}} ``` ```mustache Responsive Form Elements theme={null}
{{#response_data}} {{#success}}
{{&success_message}}
{{/success}} {{#errors}}
{{#error_message}}

{{.}}

{{/error_message}}
{{/errors}} {{/response_data}}
```
## Theme and Settings Conditionals Advanced conditional logic based on theme settings and store configuration: ```mustache Store Feature Toggles theme={null} {{#shop.login_active}}
{{#user.logged_in}} {{/user.logged_in}} {{^user.logged_in}} {{/user.logged_in}}
{{/shop.login_active}} {{#shop.cconverter_active}}
{{#shop.cconverter_currencies}} {{/shop.cconverter_currencies}}
{{/shop.cconverter_active}} {{#shop.app.languages}}
{{#shop.languages}} {{id}} {{id}} {{/shop.languages}}
{{/shop.app.languages}} ``` ```mustache Settings-Based Content theme={null} {{#settings.home_elements}} {{#is_slider}}
{{#element}} {{#title}}

{{title}}

{{/title}} {{#image1_link}}
{{title}}
{{/image1_link}} {{#page.content}}
{{&page.content}}
{{/page.content}} {{/element}}
{{/is_slider}} {{#is_title}}
{{#element}} {{#title}}

{{title}}

{{/title}} {{#page.content}}
{{&page.content}}
{{/page.content}} {{/element}}
{{/is_title}} {{/settings.home_elements}} ```
## Error Handling and Edge Cases Handle various error states and edge cases with conditionals: ```mustache Robust Error Handling theme={null} {{#response_data}} {{#success}}
{{&success_message}}
{{/success}} {{#errors}}
{{#error_message}}

{{.}}

{{/error_message}}
{{/errors}} {{/response_data}} {{#basket.isEmpty}}

{{#lang}}Your cart is empty{{/lang}}

{{#lang}}Start Shopping{{/lang}}
{{/basket.isEmpty}} {{^basket.isEmpty}}

{{basket.items_count}} {{#lang}}items in cart{{/lang}}

{{#lang}}Total{{/lang}}: {{basket.total_amount}}

{{#lang}}Checkout{{/lang}}
{{/basket.isEmpty}} ```
## Blog and Content Conditionals Handle blog and content page states: ```mustache Blog Content Logic theme={null} {{#is_list}}

{{blog.title_current}}

{{#blog.filter_tags}}
{{#available_tags}} {{title}} {{/available_tags}}
{{/blog.filter_tags}} {{#posts}}
{{#hasImage}}
{{title}}
{{/hasImage}}

{{title}}

{{&content_short}}
{{/posts}}
{{/is_list}} {{#is_post}}

{{post.title}}

{{#post.hasImage}}
{{post.title}}
{{/post.hasImage}}
{{&post.content}}
{{/is_post}} ```
## Order and Thank You Page Conditionals ```mustache Order Confirmation Logic theme={null} {{#order}}

{{#lang}}Thank you for your order{{/lang}}

{{#lang}}Order number{{/lang}}: {{order.id}}

{{#lang}}Order Summary{{/lang}}

{{#lang}}Subtotal{{/lang}} {{order.value_excl_tax}}
{{#order.tax_amount}}
{{#lang}}Tax{{/lang}} {{order.tax_amount}}
{{/order.tax_amount}} {{#order.shipping_amount}}
{{#lang}}Shipping{{/lang}} {{order.shipping_amount}}
{{/order.shipping_amount}}
{{#lang}}Total{{/lang}} {{order.value}}
{{#order.items}}

{{title}}

{{#variant}}

{{variant}}

{{/variant}}

{{#lang}}Quantity{{/lang}}: {{qty}}

{{#lang}}Price{{/lang}}: {{price}}

{{/order.items}}
{{/order}} ```
## Best Practices for Complex Conditionals Always consider what happens when conditions are false and provide appropriate content Test your conditionals with different data states, including empty and error conditions Use clear indentation and comments to make complex conditionals easy to follow ## Debugging Complex Conditionals ```mustache Debug Conditional Logic theme={null} ``` ## Next Steps Learn how to combine conditionals with helper functions Understanding what properties are available for conditional logic Explore conditional properties in store and user objects Discover advanced templating techniques and patterns # Basket Object Source: https://quickbutik.dev/theme-development/global/basket Access shopping cart data, items, and checkout functionality The `basket` object contains all information about the customer's shopping cart. It's available globally across all pages, making it perfect for cart widgets, mini-carts, and checkout processes. **Usage**: Available globally - use anywhere to display cart contents, totals, and checkout links ## Basic Cart Information Access the essential cart data for displaying cart status and totals: ```mustache Cart Overview theme={null} {{^basket.isEmpty}}
{{basket.items_count}} {{basket.total_amount}} {{#lang}}Checkout{{/lang}}
{{/basket.isEmpty}} {{#basket.isEmpty}}

{{#lang}}Your cart is empty{{/lang}}

{{/basket.isEmpty}} ``` ```mustache Cart Header Widget theme={null}
{{#basket.isEmpty}}

{{#lang}}Your cart is empty{{/lang}}

{{/basket.isEmpty}} {{^basket.isEmpty}}
{{/basket.isEmpty}}
```
## Available Properties | Property | Type | Description | | --------------------- | ------- | -------------------------------------- | | `basket.isEmpty` | Boolean | Check if cart is empty | | `basket.items_count` | Number | Number of items in cart | | `basket.total_amount` | String | Total amount for items in cart | | `basket.items` | Array | Object containing all products in cart | ## Cart Items Loop through cart items to display detailed cart contents: ```mustache Basic Cart Items theme={null} {{#basket.items}}
{{#item.firstimage}} {{item.title}} {{/item.firstimage}}

{{item.title}}

{{#is_variant}}

{{variant_name}}

{{/is_variant}}
{{item.price}} Γ— {{qty}} {{amount}}
{{/basket.items}} ``` ```mustache Detailed Cart Table theme={null} {{#basket.items}} {{/basket.items}}
{{#lang}}Product{{/lang}} {{#lang}}Price{{/lang}} {{#lang}}Quantity{{/lang}} {{#lang}}Total{{/lang}}
{{#item.firstimage}} {{item.title}} {{/item.firstimage}}

{{item.title}}

{{#lang}}SKU{{/lang}}: {{item.sku}}

{{#is_variant}}

{{variant_name}}

{{/is_variant}}
{{item.price}} {{qty}} {{amount}}
{{#lang}}Total{{/lang}} {{basket.total_amount}}
```
### Cart Item Properties When looping through `basket.items`, each item has these properties: | Property | Type | Description | | ----------------- | ------- | -------------------------------------------- | | `item.id` | String | Product ID | | `item.sku` | String | Product article number | | `item.url` | String | Product link address | | `item.title` | String | Product title | | `item.firstimage` | String | Product image | | `is_variant` | Boolean | True if it's a product variant | | `variant_name` | String | Variant designation | | `item.price` | String | Product unit price | | `qty` | Number | Quantity of product added to cart | | `amount` | String | Product total amount (unit price x quantity) | ## Checkout Integration ### Paylink Usage The `paylink` provides the correct checkout URL when the cart is not empty: ```mustache Checkout Integration theme={null} {{^basket.isEmpty}}

{{#lang}}Ready to checkout?{{/lang}}

{{basket.items_count}} {{#lang}}items{{/lang}} β€’ {{basket.total_amount}}

{{#lang}}Secure Checkout{{/lang}}
{{#lang}}SSL Secure Checkout{{/lang}}
{{/basket.isEmpty}} ```
## Mini Cart Examples ### Slide-out Cart ```mustache Slide-out Mini Cart theme={null}

{{#lang}}Shopping Cart{{/lang}}

{{#basket.isEmpty}}

{{#lang}}Your cart is empty{{/lang}}

{{#lang}}Continue Shopping{{/lang}}
{{/basket.isEmpty}} {{^basket.isEmpty}}
{{#basket.items}}
{{#item.firstimage}} {{item.title}} {{/item.firstimage}}

{{item.title}}

{{#is_variant}}

{{variant_name}}

{{/is_variant}}
{{qty}} {{item.price}}
{{amount}}
{{/basket.items}}
{{/basket.isEmpty}}
{{^basket.isEmpty}} {{/basket.isEmpty}}
```
### Cart Page Template ```mustache Full Cart Page theme={null}

{{#lang}}Shopping Cart{{/lang}}

{{#basket.isEmpty}}

{{#lang}}Your cart is empty{{/lang}}

{{#lang}}Looks like you haven't added anything to your cart yet{{/lang}}

{{#lang}}Start Shopping{{/lang}}
{{/basket.isEmpty}} {{^basket.isEmpty}}

{{#lang}}Cart Items{{/lang}} ({{basket.items_count}})

{{#basket.items}}
{{#item.firstimage}} {{item.title}} {{/item.firstimage}}

{{item.title}}

{{#lang}}SKU{{/lang}}: {{item.sku}}

{{#is_variant}}

{{variant_name}}

{{/is_variant}}
{{item.price}} Γ— {{qty}}
{{amount}}
{{/basket.items}}

{{#lang}}Order Summary{{/lang}}

{{#lang}}Subtotal{{/lang}} ({{basket.items_count}} {{#lang}}items{{/lang}}) {{basket.total_amount}}
{{#lang}}Total{{/lang}} {{basket.total_amount}}
{{/basket.isEmpty}}
```
## Mobile Cart Examples ### Mobile Cart Badge ```mustache Mobile Cart Badge theme={null}
{{^basket.isEmpty}} {{basket.items_count}} {{/basket.isEmpty}}
{{#lang}}Cart{{/lang}} {{^basket.isEmpty}} {{basket.total_amount}} {{/basket.isEmpty}} {{#basket.isEmpty}} {{#lang}}Empty{{/lang}} {{/basket.isEmpty}}
```
## Best Practices Always provide meaningful empty cart states with clear calls-to-action Use image optimization with the `{{#img}}` wrapper for cart item thumbnails Include proper ARIA labels and semantic HTML for cart interactions Design cart widgets to work well on all screen sizes ## Common Patterns ### Cart Item Counter ```mustache Dynamic Cart Counter theme={null}
{{#basket.isEmpty}} {{#lang}}Cart{{/lang}} {{/basket.isEmpty}} {{^basket.isEmpty}} {{basket.items_count}} {{#lang}}item{{/lang}}{{#basket.items_count}}s{{/basket.items_count}} {{/basket.isEmpty}}
```
### Quick Add Success Message ```mustache Add to Cart Success theme={null} {{^basket.isEmpty}}
{{#lang}}Item added to cart{{/lang}} {{#lang}}Checkout now{{/lang}}
{{/basket.isEmpty}} ```
## Next Steps Build dynamic navigation menus and breadcrumbs Learn about displaying individual product information Display order confirmation and thank you page content Explore store-wide settings and information # Navigation Object Source: https://quickbutik.dev/theme-development/global/navigation Build dynamic navigation menus and breadcrumbs using available link lists The navigation system in Quickbutik provides link lists and breadcrumbs for creating basic menus and navigation elements. These are essential for building site structure and user experience. **Usage**: Available globally - create main menus, top menus, footer links, and breadcrumbs ## Link Lists (Linklist) Link lists are customizable navigation menus that you can create and manage in your Control Panel. Three predefined link lists are available: main, top, and footer. ```mustache Basic Navigation Menu theme={null} ``` ```mustache Dropdown Navigation theme={null} ``` ## Available Link Lists | Linklist | Description | Usage | | ----------------- | ---------------------------- | ---------------------------- | | `linklist.main` | Contains theme's Main Menu | Primary navigation | | `linklist.top` | Contains theme's Top Menu | Utility/secondary navigation | | `linklist.footer` | Contains theme's Footer Menu | Footer navigation | ### Linklist Properties When looping through `linklist.{name}`, each link has these properties: | Property | Type | Description | | -------------- | ------- | ------------------------------------------------ | | `name` | String | Menu item's designation | | `url` | String | Menu item's link address | | `current` | Boolean | True if visitor is on the current menu item | | `hasChildren` | Boolean | Check if menu item has sub-menu items | | `hasChildren2` | Boolean | Check if second sub-menu item has sub-menu items | | `hasChildren3` | Boolean | Check if third sub-menu item has sub-menu items | | `hasChildren4` | Boolean | Check if fourth sub-menu item has sub-menu items | | `children` | Array | Object containing sub-menu items | ## Multi-Level Navigation Handle nested menu structures with multiple levels: ```mustache Multi-Level Menu theme={null} ``` ## Breadcrumbs Show the navigation path to help users understand their location: ```mustache Basic Breadcrumbs theme={null} ``` ```mustache Rich Breadcrumbs theme={null} ``` ### Breadcrumb Properties When using `breadcrumbs`: | Property | Type | Description | | -------- | ------- | ------------------------------------- | | `title` | String | Display text for this breadcrumb | | `url` | String | Link URL (empty for current page) | | `last` | Boolean | True if this is the current/last item | ## Complete Navigation Examples ### Full Header Navigation ```mustache Complete Header theme={null} ``` ### Footer Navigation ```mustache Complete Footer theme={null} ``` ### Mobile Navigation ```mustache Mobile Menu theme={null}

{{#lang}}Menu{{/lang}}

```
## Conditional Navigation Use global conditionals to show context-specific navigation: ```mustache Context-Aware Navigation theme={null} ``` ## Usage Notes **Limited Navigation Objects**: Only three predefined link lists are available (main, top, footer) and basic breadcrumbs. Category navigation, custom link lists, and external link properties are not supported. ## Best Practices Use proper ARIA labels, semantic HTML, and keyboard navigation support Always highlight the current page/section using the current property Design navigation that works well on small screens first Use hasChildren properties to check for sub-menus before rendering ## Complete Navigation Template ```mustache Full Navigation System theme={null} {{seo.title}}
```
## Next Steps Learn about SEO meta tags and structured data Explore store-wide settings and information Display individual product information Build basic category pages # SEO Object Source: https://quickbutik.dev/theme-development/global/seo Access basic SEO meta tags for search engine optimization The `seo` object provides basic data needed for search engine optimization. It automatically generates appropriate titles and descriptions based on the current page content. **Usage**: Available globally - use in your `` section to output basic SEO meta tags for each page ## Basic SEO Meta Tags The essential SEO elements that are available for each page's `` section: ```mustache Basic SEO Tags theme={null} {{seo.title}} {{#seo.description}} {{/seo.description}} ``` ```mustache Complete Basic SEO Head theme={null} {{seo.title}} {{#seo.description}} {{/seo.description}} ``` ## Available Properties | Property | Type | Description | | ----------------- | ------ | ----------------------------------- | | `seo.title` | String | Search title for current page | | `seo.description` | String | Search description for current page | ## Page-Specific SEO Different page types automatically generate appropriate SEO data using the available properties: ### Product Pages ```mustache Product SEO theme={null} {{seo.title}} {{#seo.description}} {{/seo.description}} {{#product.price}} {{/product.price}} {{#product.currency}} {{/product.currency}} ``` ### Category Pages ```mustache Category SEO theme={null} {{seo.title}} {{#seo.description}} {{/seo.description}} {{#category.name}} {{/category.name}} ``` ### Blog Pages ```mustache Blog SEO theme={null} {{seo.title}} {{#seo.description}} {{/seo.description}} {{#blog.title_current}} {{/blog.title_current}} ``` ## Structured Data (JSON-LD) Add basic structured data using available shop information: ```mustache Organization Schema theme={null} ``` ```mustache Product Schema theme={null} {{#isProduct}} {{/isProduct}} ``` ## Breadcrumb Schema Add breadcrumb structured data using the available breadcrumbs wrapper: ```mustache Breadcrumb Schema theme={null} ``` ## Multi-Language Support If your store supports multiple languages, add language links: ```mustache Language Links theme={null} {{#shop.languages}} {{/shop.languages}} ``` ## Complete SEO Template ```mustache Complete SEO Head Section theme={null} {{seo.title}} {{#seo.description}} {{/seo.description}} {{#shop.languages}} {{/shop.languages}} ``` ## Page Context Examples Use global conditionals to add context-specific SEO: ```mustache Context-Specific SEO theme={null} {{seo.title}} {{#seo.description}} {{/seo.description}} {{#isProduct}} {{#product.gtin}} {{/product.gtin}} {{/isProduct}} {{#isCategory}} {{/isCategory}} {{#isStart}} {{/isStart}} {{#isPage}} {{/isPage}} ``` ## Usage Notes **Limited SEO Object**: The SEO object only provides basic title and description properties. Advanced SEO features like Open Graph tags, Twitter Cards, canonical URLs, robots meta tags, and analytics integration are not available through the SEO object. ## Best Practices Keep page titles under 60 characters for optimal display in search results Meta descriptions should be 150-160 characters for best truncation The system automatically generates unique titles and descriptions for each page Use available shop and product properties to create basic structured data ## Manual SEO Enhancement For additional SEO features, you can manually add them using available properties: ```mustache Manual SEO Enhancements theme={null} {{seo.title}} {{#seo.description}} {{/seo.description}} {{#seo.description}} {{/seo.description}} {{#isProduct}} {{#product.firstimage}} {{/product.firstimage}} {{/isProduct}} {{^isProduct}} {{/isProduct}} ``` ## Next Steps Learn about displaying individual product information with SEO optimization Build SEO-optimized category pages Create SEO-friendly navigation and breadcrumbs Learn about store-wide settings and global information # Settings Object Source: https://quickbutik.dev/theme-development/global/settings Access theme-specific customizable settings and configuration The `settings` object contains theme-specific configuration options that you can customize through your Control Panel. Currently, the primary use of settings is for configurable home page elements. **Usage**: Available globally - use to access theme customization settings configured in the Control Panel **Theme Dependency**: The `settings` object is fully dependent on the theme settings available for your specific theme. Different themes may have different settings configurations. ## Theme Dependency **Important**: The `settings` object and its available properties are entirely dependent on your theme's configuration. The settings shown in this documentation represent the standard Quickbutik theme settings structure. Your specific theme may have different settings available, or may not use the settings object at all. Always check your theme's documentation or Control Panel to see what settings are actually available for your theme. ## Home Page Elements The `settings.home_elements` object allows you to create dynamic, customizable content sections for your homepage that can be managed through the admin interface: ```mustache Home Page Elements Loop theme={null} {{#settings.home_elements}} {{#is_slider}}
{{#element}}
{{#title}}

{{title}}

{{/title}} {{#image1_link}}
{{title}}
{{/image1_link}} {{#use_image2}} {{#image2_link}}
{{title}}
{{/image2_link}} {{/use_image2}} {{#page.content}}
{{&page.content}}
{{/page.content}}
{{/element}}
{{/is_slider}} {{#is_title}}
{{#element}} {{#title}}

{{title}}

{{/title}} {{#page.content}}
{{&page.content}}
{{/page.content}} {{/element}}
{{/is_title}} {{#is_title_2}}
{{#element}} {{#title}}

{{title}}

{{/title}} {{#page.content}}
{{&page.content}}
{{/page.content}} {{/element}}
{{/is_title_2}} {{#is_page}}
{{#element}} {{#title}}

{{title}}

{{/title}} {{#image1_link}}
{{title}}
{{/image1_link}} {{#page.content}}
{{&page.content}}
{{/page.content}} {{/element}}
{{/is_page}} {{/settings.home_elements}} ``` ```mustache Flexible Home Elements theme={null} {{#settings.home_elements}}
{{#element}}
{{#title}}
{{#is_title}}

{{title}}

{{/is_title}} {{#is_title_2}}

{{title}}

{{/is_title_2}} {{#is_slider}}

{{title}}

{{/is_slider}} {{#is_page}}

{{title}}

{{/is_page}}
{{/title}}
{{#image1_link}}
{{title}}
{{/image1_link}} {{#use_image2}} {{#image2_link}}
{{title}}
{{/image2_link}} {{/use_image2}}
{{#page.content}}
{{&page.content}}
{{/page.content}}
{{/element}}
{{/settings.home_elements}} ```
## Available Properties ### Home Elements Structure | Property | Type | Description | | ------------------------ | ----- | ------------------------------------------- | | `settings.home_elements` | Array | Dynamic elements for homepage customization | ### Element Type Conditionals Within the `settings.home_elements` loop: | Property | Type | Description | | ------------ | ------- | -------------------------------------- | | `is_slider` | Boolean | Check if element/section is a slider | | `is_title` | Boolean | Check if element/section is a title | | `is_title_2` | Boolean | Check if element/section is a subtitle | | `is_page` | Boolean | Check if element/section is a page | ### Element Properties Within `settings.home_elements.element`: | Property | Type | Description | | -------------- | ------- | ----------------------------- | | `title` | String | Element title | | `image1_link` | String | Image 1 link content | | `use_image2` | Boolean | Should image 2 be used | | `image2_link` | String | Image 2 link content | | `page.content` | String | Page content for page element | ## Element Type Examples ### Slider Elements ```mustache Slider Implementation theme={null} {{#settings.home_elements}} {{#is_slider}}
{{#element}}
{{#image1_link}} {{title}} {{/image1_link}}
{{#title}}

{{title}}

{{/title}} {{#page.content}}
{{&page.content}}
{{/page.content}} {{#use_image2}} {{#image2_link}}
{{title}}
{{/image2_link}} {{/use_image2}}
{{/element}}
{{/is_slider}} {{/settings.home_elements}} ```
### Title Elements ```mustache Title Sections theme={null} {{#settings.home_elements}} {{#is_title}}
{{#element}} {{#title}}

{{title}}

{{/title}} {{#page.content}}
{{&page.content}}
{{/page.content}} {{#image1_link}}
{{title}}
{{/image1_link}} {{/element}}
{{/is_title}} {{#is_title_2}}
{{#element}} {{#title}}

{{title}}

{{/title}} {{#page.content}}
{{&page.content}}
{{/page.content}} {{/element}}
{{/is_title_2}} {{/settings.home_elements}} ```
### Page Elements ```mustache Page Content Sections theme={null} {{#settings.home_elements}} {{#is_page}}
{{#element}}
{{#title}}

{{title}}

{{/title}}
{{#image1_link}}
{{title}}
{{/image1_link}} {{#page.content}}
{{&page.content}}
{{/page.content}} {{#use_image2}} {{#image2_link}}
{{title}}
{{/image2_link}} {{/use_image2}}
{{/element}}
{{/is_page}} {{/settings.home_elements}} ```
## Complete Homepage Example ```mustache Full Homepage Implementation theme={null} {{seo.title}}
{{#settings.home_elements}}
{{#is_slider}}
{{#element}}
{{#image1_link}}
{{/image1_link}} {{^image1_link}}
{{/image1_link}}
{{#title}}

{{title}}

{{/title}} {{#page.content}}
{{&page.content}}
{{/page.content}} {{#use_image2}} {{#image2_link}}
{{title}}
{{/image2_link}} {{/use_image2}}
{{/element}}
{{/is_slider}} {{#is_title}}
{{#element}} {{#title}}

{{title}}

{{/title}} {{#page.content}}
{{&page.content}}
{{/page.content}} {{#image1_link}}
{{title}}
{{/image1_link}} {{/element}}
{{/is_title}} {{#is_title_2}}
{{#element}} {{#title}}

{{title}}

{{/title}} {{#page.content}}
{{&page.content}}
{{/page.content}} {{/element}}
{{/is_title_2}} {{#is_page}}
{{#element}}
{{#title}}

{{title}}

{{/title}}
{{#image1_link}}
{{title}}
{{/image1_link}}
{{#page.content}} {{&page.content}} {{/page.content}} {{#use_image2}} {{#image2_link}}
{{title}}
{{/image2_link}} {{/use_image2}}
{{/element}}
{{/is_page}}
{{/settings.home_elements}}
```
## Best Practices Use home elements to give store owners flexible content control without code changes Always use the `{{#img}}` wrapper with appropriate dimensions for different element types Design elements to work well across all device sizes Use lazy loading for images and optimize element rendering ## Common Implementation Patterns ### Conditional Element Display ```mustache Element Type Detection theme={null} {{#settings.home_elements}}
{{#element}} {{/element}}
{{/settings.home_elements}} ```
### Image Fallbacks ```mustache Image with Fallbacks theme={null} {{#element}} {{#image1_link}} {{title}} {{/image1_link}} {{^image1_link}}
{{#lang}}No image available{{/lang}}
{{/image1_link}} {{/element}} ```
## Next Steps Learn about SEO meta tags and structured data Explore store-wide settings and information Build dynamic navigation menus and breadcrumbs Learn about theme development best practices # Shop Object Source: https://quickbutik.dev/theme-development/global/shop Access store settings, configuration, and global information The `shop` object contains your store's fundamental settings and configuration. It's available globally across all pages and templates, making it perfect for headers, footers, and any store-wide functionality. **Usage**: Available globally - use anywhere in your templates to access store information ## Basic Store Information Access your store's core details that you've configured in the Control Panel: ```mustache Store Basics theme={null}

{{shop.name}}

{{&shop.contact_text}}
```
### Available Properties | Property | Description | Example | | ------------------- | -------------------- | ----------------------- | | `shop.name` | Your store's name | `"My Awesome Store"` | | `shop.url` | Your store's URL | `"https://mystore.com"` | | `shop.address` | Street address | `"123 Main Street"` | | `shop.zipcode` | Postal code | `"12345"` | | `shop.city` | City name | `"Stockholm"` | | `shop.phone` | Phone number | `"+46 123 456 789"` | | `shop.contact_text` | Contact page content | HTML content | ## Currency Conversion If you have multiple currencies enabled, display a currency picker: ```mustache Currency Picker theme={null} {{#shop.cconverter_active}}
{{/shop.cconverter_active}} ``` ```mustache Advanced Currency Picker theme={null} {{#shop.cconverter_active}}
{{#shop.cconverter_currencies}} {{currency}} {{/shop.cconverter_currencies}}
{{/shop.cconverter_active}} ```
### Currency Object Properties When looping through `shop.cconverter_currencies`: | Property | Description | | ---------- | --------------------------------------------------------- | | `currency` | Currency code (e.g., "SEK", "EUR", "USD") | | `current` | Boolean - true if this is the currently selected currency | ## Multi-Language Support Display language options when multiple languages are configured: ```mustache Language Selector theme={null} {{#shop.app.languages}}

{{#lang}}Choose Language{{/lang}}

{{#shop.languages}} {{id}} {{id}} {{/shop.languages}}
{{/shop.app.languages}} ``` ```mustache Compact Language Switcher theme={null} {{#shop.app.languages}}
{{#shop.languages}} {{id}} {{/shop.languages}}
{{/shop.app.languages}} ```
### Language Object Properties When looping through `shop.languages`: | Property | Description | | ------------- | -------------------------------------------- | | `id` | Language identifier (e.g., "en", "sv", "no") | | `url` | URL to switch to this language | | `asset_flags` | Helper to get flag image for this language | ## Tax Toggle If tax toggling is enabled, allow customers to switch between prices including/excluding tax: ```mustache Tax Toggle theme={null} {{#shop.taxtoggle_active}}
{{#shop.exclude_tax}} {{#lang}}Tax Excl.{{/lang}} {{/shop.exclude_tax}} {{^shop.exclude_tax}} {{#lang}}Tax Incl.{{/lang}} {{/shop.exclude_tax}}
{{/shop.taxtoggle_active}} ``` ```mustache Tax Status Display theme={null}
{{#shop.exclude_tax}} {{#lang}}Prices excluding tax{{/lang}} {{/shop.exclude_tax}} {{^shop.exclude_tax}} {{#lang}}Prices including tax{{/lang}} {{/shop.exclude_tax}}
```
### Tax-Related Properties | Property | Description | | ----------------------- | -------------------------------------------------------- | | `shop.taxtoggle_active` | Boolean - true if tax toggle is enabled | | `shop.exclude_tax` | Boolean - true if currently showing prices excluding tax | | `shop.incl_tax_url` | URL to switch to prices including tax | | `shop.excl_tax_url` | URL to switch to prices excluding tax | ## Customer Login Integration Display login/account links when customer accounts are enabled: ```mustache Login/Account Links theme={null} {{#shop.login_active}} {{/shop.login_active}} ``` ```mustache Header Account Menu theme={null} {{#shop.login_active}} {{/shop.login_active}} ``` ## Stock Messages Display custom out-of-stock messages: ```mustache Stock Messages theme={null} {{#product.soldOut}}
{{#shop.soldout_text}} {{shop.soldout_text}} {{/shop.soldout_text}} {{^shop.soldout_text}} {{#lang}}Sorry, this item is currently out of stock{{/lang}} {{/shop.soldout_text}}
{{/product.soldOut}} ```
## Complete Examples ### Store Header with All Features ```mustache Complete Store Header theme={null}
{{#shop.cconverter_active}}
{{/shop.cconverter_active}} {{#shop.app.languages}}
{{#shop.languages}} {{id}} {{/shop.languages}}
{{/shop.app.languages}} {{#shop.taxtoggle_active}}
{{#shop.exclude_tax}} {{#lang}}Incl. Tax{{/lang}} {{/shop.exclude_tax}} {{^shop.exclude_tax}} {{#lang}}Excl. Tax{{/lang}} {{/shop.exclude_tax}}
{{/shop.taxtoggle_active}} {{#shop.login_active}} {{/shop.login_active}}
```
### Store Footer with Contact Information ```mustache Store Footer theme={null} ``` ## Best Practices Always check if features are enabled before displaying related UI elements (e.g., `{{#shop.cconverter_active}}`) Provide fallback content when optional settings aren't configured Structure your shop information with proper semantic HTML elements Shop data is cached and updates when you change store settings in the Control Panel ## Next Steps Learn about customer account and authentication data Explore theme-specific customizable settings Work with shopping cart data and functionality Build dynamic navigation menus # User Object Source: https://quickbutik.dev/theme-development/global/user Access customer account and authentication information The `user` object contains information about logged-in customers when the login/account feature is enabled in your store. It's used to create account management functionality and personalized experiences. **Usage**: Available globally when `shop.login_active` is true - check login status and display account-related content ## Basic Usage The user object helps you create login/logout functionality and personalized content: ```mustache Basic Login Status theme={null} {{#shop.login_active}} {{#user.logged_in}}

Welcome back!

{{#lang}}My Account{{/lang}} {{/user.logged_in}} {{^user.logged_in}} {{#lang}}Log In{{/lang}} {{/user.logged_in}} {{/shop.login_active}} ``` ```mustache Account Navigation theme={null} {{#shop.login_active}} {{/shop.login_active}} ```
## Available Properties | Property | Type | Description | | ---------------- | ------- | -------------------------------------- | | `user.logged_in` | Boolean | Check if visitor is logged in | | `user.login_url` | String | Output link to Login / My Account page | ## Conditional Content Examples ### Personalized Header ```mustache Personalized Header theme={null} ``` ### Account Dashboard Link ```mustache Account Dashboard theme={null} {{#shop.login_active}} {{#user.logged_in}}

{{#lang}}Account Dashboard{{/lang}}

{{/user.logged_in}} {{/shop.login_active}} ```
### Checkout Experience ```mustache Checkout Login Prompt theme={null} {{#shop.login_active}} {{^user.logged_in}}

{{#lang}}Returning Customer?{{/lang}}

{{#lang}}Sign in for faster checkout{{/lang}}

{{/user.logged_in}} {{#user.logged_in}}

{{#lang}}Signed in and ready to checkout{{/lang}}

{{/user.logged_in}} {{/shop.login_active}} ```
## Complete Examples ### Responsive Account Menu ```mustache Complete Account Menu theme={null} {{#shop.login_active}}
{{#user.logged_in}} {{/user.logged_in}} {{^user.logged_in}} {{/user.logged_in}}
{{/shop.login_active}} ```
### Mobile-Friendly Account Section ```mustache Mobile Account Section theme={null} {{#shop.login_active}}
{{#user.logged_in}} {{/user.logged_in}} {{^user.logged_in}} {{/user.logged_in}}
{{/shop.login_active}} ```
## Implementation Notes Always wrap user object usage with `{{#shop.login_active}}` to ensure the feature is enabled The `user.login_url` leads to the same page for both login and account management Logout is typically handled via `/logout` endpoint, not through the login\_url Use `{{#user.logged_in}}` and `{{^user.logged_in}}` for clear conditional logic ## Best Practices ### Accessible Login Forms ```mustache Accessible Login theme={null} {{#shop.login_active}} {{^user.logged_in}}

{{#lang}}Access your account to view orders and manage settings{{/lang}}

{{/user.logged_in}} {{/shop.login_active}} ```
### SEO-Friendly Account Links ```mustache SEO Account Links theme={null} {{#shop.login_active}} {{#user.logged_in}} {{/user.logged_in}} {{/shop.login_active}} ``` ## Next Steps Learn about theme-specific customizable settings Work with shopping cart data and functionality Display order information on thank you pages Back to shop settings and global store information # Introduction Source: https://quickbutik.dev/theme-development/introduction Build beautiful, dynamic storefronts with Quickbutik's powerful templating system Welcome to Quickbutik's theme development documentation! This guide will teach you everything you need to know about building custom storefronts using our powerful Mustache-based templating system. **What you'll learn:** * How to work with Quickbutik's theme engine * Mustache templating language fundamentals * Available objects and data structures * Best practices for theme development ## What is Quickbutik Theme Development? Quickbutik uses **Mustache**, an open-source logic-less templating language, to create dynamic ecommerce storefronts. With our templating system, you can customize every aspect of the shopping experience. ## Key Concepts Before diving in, let's understand the core concepts you'll work with: **Data containers** that hold information like products, orders, or store settings. Example: `{{product.title}}` **Properties** of objects that contain specific data. Example: `{{product.price}}`, `{{customer.email}}` **If-statements** that show content based on conditions. Example: `{{#product.soldOut}}Out of stock{{/product.soldOut}}` **Helper functions** that process and transform data. Example: `{{#img}}{{product.image}}_400x400{{/img}}` ## Template Structure All Quickbutik themes follow a structured approach where dynamic content is embedded using double curly braces: ```mustache theme={null} {{object.attribute}} ``` ### Basic Examples ```mustache Product Title theme={null}

{{product.title}}

``` ```mustache Price with Currency theme={null} {{product.price}} {{product.currency}} ``` ```mustache Conditional Content theme={null} {{#product.soldOut}}
Out of stock
{{/product.soldOut}} {{^product.soldOut}} {{/product.soldOut}} ```
## Where to Edit Your Theme You can access and modify your theme code through the Quickbutik Control Panel: Navigate to **Appearance β†’ Theme β†’ Under the Hood** in your control panel Explore the different template files like `product.mustache`, `list.mustache`, `cartsuccess.mustache` Make changes to the code and use the preview function to see your changes Save your changes and publish them to your live store ## Common Use Cases Here are some popular things you can accomplish with theme development: Create unique product layouts, image galleries, and variant selectors tailored to your brand. ```mustache theme={null} {{#product.images}} {{alttext}} {{/product.images}} ``` Build responsive menus that adapt based on your store's categories and pages. ```mustache theme={null} ``` Display cart contents, quantities, and totals anywhere in your theme. ```mustache theme={null} {{^basket.isEmpty}}
{{basket.items_count}} items - {{basket.total_amount}} Checkout
{{/basket.isEmpty}} ```
Create themes that work seamlessly with Quickbutik's translation system. ```mustache theme={null} ```
## Development Workflow **Pro tip**: Always test your changes in a theme under construction before publishing to your live version! ### Recommended Development Process 1. **Plan your layout** - Sketch out the design and identify what data you need 2. **Start with static HTML** - Build the basic structure without dynamic content 3. **Add dynamic elements** - Replace static content with Mustache templates 4. **Test thoroughly** - Check different scenarios (empty cart, sold out products, etc.) 5. **Optimize performance** - Minimize template complexity and optimize images ## Getting Help Use the search function (top left) or browser search to quickly find specific objects or functions Technical questions? Email us at [support@quickbutik.com](mailto:support@quickbutik.com) Join our developer community for tips, tricks, and peer support Browse our comprehensive examples library for common implementation patterns ## Next Steps Ready to start building? Here's your learning path: Build your first custom template in 15 minutes Learn the fundamental syntax and concepts Understand how Quickbutik themes are organized Set up your local development environment *** **Important**: Always backup your theme before making significant changes. You can download a copy of your theme files from the control panel. # Mustache Template Basics Source: https://quickbutik.dev/theme-development/mustache-basics Master the fundamentals of Mustache templating for Quickbutik themes Mustache is a logic-less templating language that Quickbutik uses to create dynamic storefronts. This guide covers the core syntax and fundamental concepts you need to get started with Mustache templates. **Why Mustache?** * Simple, readable syntax * Logic-less design prevents complex business logic in templates * Cross-platform compatibility * Secure by design - prevents code injection ## Template Syntax Overview All Mustache templates use double curly braces `{{}}` to denote dynamic content. Here are the basic patterns: `{{variable}}` - Outputs the value of a variable `{{#section}}...{{/section}}` - Conditional blocks or loops `{{^section}}...{{/section}}` - Shows content when condition is false `{{! This is a comment }}` - Not rendered in output ## Variables and Output The simplest Mustache tag outputs a variable's value: ```mustache Basic Variables theme={null}

{{product.title}}

Price: {{product.price}} {{product.currency}}

SKU: {{product.sku}}

``` ```html Expected Output theme={null}

Amazing T-Shirt

Price: 299 SEK

SKU: SHIRT-001

```
### HTML Escaping By default, Mustache escapes HTML characters for security. Use `{{{variable}}}` or `{{&variable}}` to render unescaped HTML: ```mustache HTML Escaping Examples theme={null}
{{product.description}}
{{&product.description}}
{{{product.description}}}
``` ```html Output Comparison theme={null}
<strong>Bold text</strong>
Bold text
```
**Security Note**: Only use unescaped output (`{{&}}` or `{{{}}}`) with trusted content like product descriptions that you control. Never use it with user-generated content. ## Basic Sections and Conditionals Sections are the core of Mustache's logic. They render content based on the truthiness of a value: ```mustache Simple Conditionals theme={null} {{#product.has_before_price}}
SALE!
{{product.before_price}} {{/product.has_before_price}} {{^product.soldOut}} {{/product.soldOut}} {{#product.soldOut}}
Sorry, this item is sold out
{{/product.soldOut}} ```
### Basic Loops When a section's value is an array, Mustache loops through each item: ```mustache Simple Loops theme={null} {{#basket.items}}
{{item.title}} - {{qty}}
{{/basket.items}} {{^basket.items}}

Your cart is empty

{{/basket.items}} ```
## Basic Object Access Use dot notation to access object properties: ```mustache Object Properties theme={null} {{product.title}} {{product.price}} {{shop.name}} {{order.customer.firstname}} {{order.customer.lastname}} {{order.customer.ship_address}} ``` ### Understanding Context Inside a section, the context changes to that object: ```mustache Context Changes theme={null}

Welcome to {{shop.name}}

{{#basket.items}}

{{item.title}}

Quantity: {{qty}}

Price: {{item.price}}

{{/basket.items}} {{#product}}

{{title}}

{{description}}

{{/product}} ```
## Helper Functions Overview Quickbutik provides special wrapper functions for common tasks: ```mustache Helper Function Examples theme={null} {{product.title}} ``` **Learn More**: Helper functions are covered in detail in the [Wrappers and Functions](/theme-development/wrappers-and-functions) guide. ## Best Practices Mustache is designed to be logic-less. Keep business logic in your data, not your templates. Use clear, descriptive variable names in your templates for better maintainability. Always provide fallbacks for empty arrays and missing data. Use default (escaped) output for any user-generated content. ### Template Organization ```mustache Good Structure theme={null} {{! Product Gallery Section }} {{! Product Information Section }}

{{product.title}}

{{! Price Display }}
{{#product.has_before_price}} {{product.before_price}} {{/product.has_before_price}} {{product.price}}
```
## Common Mistakes to Avoid ```mustache theme={null} {{#product.soldOut}}

Sold out!

{{#product.soldOut}}

Sold out!

{{/product.soldOut}} ```
```mustache theme={null} {{#basket.items}}

{{product.title}}

{{/basket.items}} {{#basket.items}}

{{item.title}}

{{/basket.items}} ```
```mustache theme={null}

{{{product.title}}}

{{product.title}}

{{&product.description}}
```
## Testing Your Templates Always test your templates using Quickbutik's preview feature before publishing Test with products that have no images, are sold out, or have no variants Make sure your templates work across different contexts (product pages, category pages, etc.) Use browser developer tools to ensure your templates generate valid HTML ## Next Steps Now that you understand the basics, dive deeper into specific aspects: Learn about all the data objects and how to access their properties Master advanced conditional logic and pattern matching Discover all available helper functions and their usage Explore store-wide data like settings, navigation, and cart # Objects and Attributes Source: https://quickbutik.dev/theme-development/objects-and-attributes Understanding Mustache objects, attributes, and property access in Quickbutik themes Objects and attributes form the foundation of Mustache templating in Quickbutik. Understanding how to access and manipulate object properties is essential for building dynamic, data-driven themes using only supported Quickbutik properties. ## What are Objects? Objects in Mustache are data structures that contain information about your store, products, users, and other entities. Each object has attributes (properties) that hold specific values. ```mustache Basic Object Access theme={null}

{{shop.name}}

{{shop.contact_text}}

{{product.title}}

Price: {{product.price}}

``` ```mustache Nested Object Access theme={null}

{{shop.address}}

{{shop.city}}, {{shop.zipcode}}

{{#product.images}} {{alttext}} {{/product.images}} ```
## Available Object Types Quickbutik provides several types of objects you can work with: ### Global Objects Available on all pages throughout your theme: | Object | Description | Availability | | ---------- | ------------------------------ | ------------ | | `shop` | Store information and settings | All pages | | `user` | Current user/customer data | All pages | | `basket` | Shopping cart contents | All pages | | `linklist` | Menu and navigation data | All pages | | `settings` | Theme customization options | All pages | | `seo` | SEO meta information | All pages | ### Page-Specific Objects Available only on specific page types: | Object | Description | Available On | | ---------- | ----------------------------- | --------------------- | | `product` | Individual product data | Product pages | | `category` | Category and product listings | Category pages | | `order` | Order confirmation details | Order/Thank you pages | | `blog` | Blog post and article data | Blog pages | | `page` | Static page content | Static pages | ## Object Property Access ### Dot Notation for Nested Properties Use dot notation to access nested properties: ```mustache Property Access Patterns theme={null} {{shop.name}} {{product.title}} {{user.login_url}} {{order.customer.email}} {{order.customer.firstname}} {{order.customer.ship_address}} {{settings.home_elements}} ``` ### Array Index Access Access specific array items by their index position: ```mustache Array Index Examples theme={null} {{product.images.0.image}} {{linklist.main.0.name}} {{product.images.1.image}} {{shop.cconverter_currencies.1.currency}} {{linklist.main.0.children.0.name}} ``` ## Working with Different Property Types Understanding the different types of data that object properties can contain: ### String Properties ```mustache Text Data theme={null}

{{product.title}}

{{product.description}}

{{product.sku}}

{{shop.contact_text}}

```
### Number Properties ```mustache Numeric Data theme={null}

Price: {{product.price}}

Items: {{basket.items_count}}

Order ID: {{order.id}}

Quantity: {{qty}}

```
### Boolean Properties ```mustache Boolean Values theme={null} {{#product.soldOut}} {{#lang}}Sold Out{{/lang}} {{/product.soldOut}} {{#product.has_before_price}} {{#lang}}Sale{{/lang}} {{/product.has_before_price}} {{#user.logged_in}}

{{#lang}}Welcome back{{/lang}}

{{/user.logged_in}} {{#basket.isEmpty}}

{{#lang}}Your cart is empty{{/lang}}

{{/basket.isEmpty}} ```
### Array Properties ```mustache Array Data theme={null} {{#product.images}} {{alttext}} {{/product.images}} {{#linklist.main}} {{name}} {{/linklist.main}} {{#order.items}}

{{title}}

{{#lang}}Quantity{{/lang}}: {{qty}}

{{/order.items}} ```
### Object Properties (Nested Objects) ```mustache Nested Objects theme={null}

{{order.customer.ship_address}}

{{order.customer.ship_city}}, {{order.customer.ship_zipcode}}

{{order.customer.ship_country}}

{{#settings.home_elements}} {{#element}}

{{title}}

{{#image1_link}} {{title}} {{/image1_link}} {{/element}} {{/settings.home_elements}} ```
## Common Object Usage Patterns ### Store Information Display ```mustache Shop Object Examples theme={null}
{{#shop.address}}

{{shop.address}}

{{shop.zipcode}} {{shop.city}}

{{/shop.address}} {{#shop.phone}}

{{#lang}}Phone{{/lang}}: {{shop.phone}}

{{/shop.phone}} {{#shop.contact_text}}
{{&shop.contact_text}}
{{/shop.contact_text}}
```
### Product Data Management ```mustache Product Object Examples theme={null}

{{product.title}}

{{#product.has_before_price}} {{product.before_price}} {{/product.has_before_price}} {{product.price}} {{product.currency}}

{{#lang}}SKU{{/lang}}: {{product.sku}}

{{#product.gtin}}

{{#lang}}EAN{{/lang}}: {{product.gtin}}

{{/product.gtin}}
{{#product.soldOut}} {{#shop.soldout_text}} {{shop.soldout_text}} {{/shop.soldout_text}} {{^shop.soldout_text}} {{#lang}}Out of Stock{{/lang}} {{/shop.soldout_text}} {{/product.soldOut}} {{^product.soldOut}} {{#lang}}Available{{/lang}} {{/product.soldOut}}
{{#product.description}}
{{&product.description}}
{{/product.description}}
```
### User Account Information ```mustache User Object Examples theme={null} {{#shop.login_active}} {{#user.logged_in}}

{{#lang}}Welcome back{{/lang}}

{{/user.logged_in}} {{^user.logged_in}} {{/user.logged_in}} {{/shop.login_active}} ```
## Working with Context and Parent Access When iterating through arrays or working within sections, you may need to access parent object properties: ```mustache Parent Context Access theme={null} {{#product.options}}

{{option_title}}

{{#option_values}}
{{name}}

{{#lang}}For{{/lang}}: {{../../title}}

{{/option_values}}
{{/product.options}} {{#related_products}}

{{rp.title}}

{{rp.price}}

{{#lang}}Currency{{/lang}}: {{../shop.name}}

{{/related_products}} ```
## Object Property Debugging When working with objects, you might need to debug what properties are available: ```mustache Debug Object Properties theme={null} {{#product.description}}
{{&product.description}}
{{/product.description}} {{^product.description}}
{{#lang}}No description available{{/lang}}
{{/product.description}} {{#product.images}}
{{#lang}}Images available{{/lang}}
{{/product.images}} {{^product.images}}
{{#lang}}No images available{{/lang}}
{{/product.images}} ```
## Best Practices Always check if a property exists before using it to avoid template errors Learn the structure of objects to access nested properties efficiently Provide fallbacks for optional properties that might not always be present ## Common Property Access Mistakes **Property Access Errors**: These common mistakes can break your templates: 1. **Wrong property names**: `{{product.name}}` instead of `{{product.title}}` 2. **Missing context**: Forgetting `../` when accessing parent properties 3. **Array confusion**: Using `{{product.images}}` instead of iterating with `{{#product.images}}` 4. **Case sensitivity**: `{{Product.title}}` instead of `{{product.title}}` ## Advanced Object Patterns ### Conditional Property Display ```mustache Smart Property Display theme={null}
{{#product.supplier_name}}

{{#lang}}Supplier{{/lang}}: {{product.supplier_name}}

{{/product.supplier_name}} {{#product.supplier_sku}}

{{#lang}}Supplier SKU{{/lang}}: {{product.supplier_sku}}

{{/product.supplier_sku}} {{#product.gtin}}

{{#lang}}EAN{{/lang}}: {{product.gtin}}

{{/product.gtin}}
```
### Object Property Chaining ```mustache Property Chain Examples theme={null} {{settings.home_elements.element.title}} {{order.customer.ship_address}} {{product.options.option_values.name}} {{#settings.home_elements}} {{#element}} {{#title}}

{{title}}

{{/title}} {{#page.content}}
{{&page.content}}
{{/page.content}} {{/element}} {{/settings.home_elements}} ```
## Supported Object Properties Reference ### Shop Object Properties ```mustache Shop Properties theme={null} {{shop.name}} {{shop.url}} {{shop.address}} {{shop.zipcode}} {{shop.city}} {{shop.phone}} {{shop.contact_text}} {{shop.soldout_text}} {{#shop.cconverter_active}}...{{/shop.cconverter_active}} {{#shop.app.languages}}...{{/shop.app.languages}} {{#shop.taxtoggle_active}}...{{/shop.taxtoggle_active}} {{#shop.login_active}}...{{/shop.login_active}} {{#shop.cconverter_currencies}} {{currency}} {{#current}}...{{/current}} {{/shop.cconverter_currencies}} {{#shop.languages}} {{id}} {{url}} {{#asset_flags}}{{id}}{{/asset_flags}} {{/shop.languages}} {{#shop.exclude_tax}}...{{/shop.exclude_tax}} {{shop.incl_tax_url}} {{shop.excl_tax_url}} ``` ### Product Object Properties ```mustache Product Properties theme={null} {{product.title}} {{product.id}} {{product.sku}} {{product.gtin}} {{product.price}} {{product.price_raw}} {{product.before_price}} {{product.currency}} {{product.description}} {{product.firstimage}} {{product.secondimage}} {{#product.images}} {{image}} {{image_id}} {{alttext}} {{/product.images}} {{product.supplier_name}} {{product.supplier_sku}} {{#product.soldOut}}...{{/product.soldOut}} {{#product.has_before_price}}...{{/product.has_before_price}} {{#product.hasOptions}}...{{/product.hasOptions}} {{#product.options}} {{option_title}} {{#option_values}} {{id}} {{name}} {{/option_values}} {{/product.options}} {{product.datafield_1}} {{product.datafield_2}} ``` ## Next Steps Learn how to use conditional logic with object properties Discover helper functions to manipulate object data Explore all available global objects in detail Learn about page-specific objects and their properties # Blog Object Source: https://quickbutik.dev/theme-development/pages/blog Display blog posts and articles using the basic blog functionality The `blog` object contains basic information about blog posts and articles. It's available on blog pages and provides access to post listings, individual posts, and basic blog functionality. **Usage**: Available on blog pages - use to display blog posts and basic blog functionality ## Blog Context Detection Check what type of blog page is being displayed: ```mustache Blog Context Detection theme={null} {{#is_list}}

{{blog.title_current}}

{{#isBlogTag}}

{{#lang}}Posts tagged with{{/lang}}: {{current_tag}}

{{/isBlogTag}} {{#posts}} {{/posts}}
{{/is_list}} {{#is_post}}

{{post.title}}

{{/is_post}} ```
## Blog Posts Listing Display multiple blog posts using the posts loop: ```mustache Blog Posts Grid theme={null} {{#is_list}}

{{blog.title_current}}

{{#isBlogTag}}

{{#lang}}Showing posts with selected tag{{/lang}}

{{/isBlogTag}}
{{#posts}}
{{#hasImage}}
{{title}}
{{/hasImage}}

{{title}}

{{#content_short}}
{{&content_short}}
{{/content_short}} {{#lang}}Read More{{/lang}}
{{/posts}}
{{/is_list}} ``` ```mustache Blog Posts List theme={null} {{#is_list}}

{{blog.title_current}}

{{#blog.rss}} {{#lang}}Subscribe to RSS{{/lang}} {{/blog.rss}}
{{#posts}} {{/posts}}
{{/is_list}} ```
## Single Blog Post Display individual blog post content: ```mustache Single Blog Post theme={null} {{#is_post}}

{{post.title}}

{{#post.hasImage}}
{{post.title}}
{{/post.hasImage}}
{{&post.content}}
{{/is_post}} ```
## Blog Tags Display available blog tags when tag filtering is active: ```mustache Blog Tags theme={null} {{#blog.filter_tags}}

{{#lang}}Browse by Tags{{/lang}}

{{#available_tags}}
{{#available_tags}} {{/available_tags}}
{{/available_tags}}
{{/blog.filter_tags}} ``` ```mustache Blog Tags Navigation theme={null} {{#blog.filter_tags}} {{/blog.filter_tags}} ```
## Available Properties ### Blog Context Properties | Property | Type | Description | | ----------- | ------- | ------------------------------------------ | | `is_list` | Boolean | Check if blog overview is being shown | | `is_post` | Boolean | Check if specific blog post is being shown | | `isBlogTag` | Boolean | Is blog overview shown based on a tag | ### Blog Properties | Property | Type | Description | | --------------------- | ------- | -------------------------------- | | `blog.url` | String | Blog link address | | `blog.title_current` | String | Blog title | | `blog.rss` | String | Blog RSS link address | | `blog.filter_tags` | Boolean | Check if tags are active | | `blog.available_tags` | Array | Object with blog tags | | `blog.posts` | Array | Object containing all blog posts | ### Available Tags Properties (within available\_tags loop) | Property | Type | Description | | -------- | ------ | --------------- | | `title` | String | Tag designation | | `url` | String | Tag URL | ### Posts Properties (within posts loop) | Property | Type | Description | | --------------- | ------- | ------------------------------- | | `hasImage` | Boolean | Check if blog post has an image | | `image` | String | Blog post image | | `title` | String | Blog post title | | `content_short` | String | Short preview of blog post | | `content` | String | Blog post content | | `author` | String | Blog post author | | `date_pretty` | String | Blog post date | | `url` | String | Blog post link address | ### Post Properties (single post context) | Property | Type | Description | | -------------------- | ------- | ------------------------------- | | `post.hasImage` | Boolean | Check if blog post has an image | | `post.image` | String | Blog post image | | `post.title` | String | Blog post title | | `post.content_short` | String | Short preview of blog post | | `post.content` | String | Blog post content | | `post.author` | String | Blog post author | | `post.date_pretty` | String | Blog post date | | `post.url` | String | Blog post link address | ## Complete Blog Template ```mustache Complete Blog Template theme={null}
{{#is_list}}

{{blog.title_current}}

{{#blog.rss}} {{#lang}}RSS Feed{{/lang}} {{/blog.rss}}
{{#isBlogTag}}

{{#lang}}Filtered by tag{{/lang}}

{{/isBlogTag}} {{#blog.filter_tags}} {{/blog.filter_tags}}
{{#posts}}
{{#hasImage}}
{{title}}
{{/hasImage}}
{{/posts}}
{{/is_list}} {{#is_post}}

{{post.title}}

{{#post.hasImage}}
{{post.title}}
{{/post.hasImage}}
{{&post.content}}
{{/is_post}}
```
## Usage Notes **Limited Blog Object**: The blog object only provides basic functionality (post listings, individual posts, and simple tag filtering). Advanced features like categories, related posts, pagination, SEO properties, and complex metadata are not available. ## Best Practices Always use is\_list and is\_post to determine the correct template structure Check hasImage before displaying images and use proper image optimization Use content\_short for excerpts and full content for complete posts Implement tag filtering when blog.filter\_tags is available ## Common Blog Layouts | Layout Type | Context | Key Elements | | ------------ | --------- | ------------------------------------ | | Blog List | is\_list | Posts loop, title\_current, RSS link | | Single Post | is\_post | Individual post content, navigation | | Tagged Posts | isBlogTag | Filtered posts, tag navigation | ## Next Steps Build blog navigation and breadcrumbs Basic SEO optimization for blog pages Learn about creating static content pages Learn about product page implementation # Category Object Source: https://quickbutik.dev/theme-development/pages/category Display category pages with basic category information The `category` object contains basic information about product categories. It's available on product list pages and provides access to category name and description fields. **Usage**: Available on product list pages - use to display basic category information ## Basic Category Information Access the core category details that are available: ```mustache Basic Category Display theme={null}

{{category.name}}

{{#category.description1}}
{{&category.description1}}
{{/category.description1}} {{#category.description2}}
{{&category.description2}}
{{/category.description2}}
``` ```mustache Category with Breadcrumbs theme={null}

{{category.name}}

{{#category.description1}}
{{&category.description1}}
{{/category.description1}} {{#category.description2}}
{{&category.description2}}
{{/category.description2}}
```
## Available Properties ### Core Category Properties | Property | Type | Description | | ----------------------- | ------ | ---------------------------- | | `category.name` | String | Category name | | `category.description1` | String | Category description field 1 | | `category.description2` | String | Category description field 2 | ## Product Listings For product listings within categories, you'll need to use the product objects as they become available within product loops: ```mustache Basic Product Display in Category Context theme={null}

{{#lang}}Products in{{/lang}} {{category.name}}

{{#category.description1}}
{{&category.description1}}
{{/category.description1}} {{#category.description2}}

{{#lang}}Additional Information{{/lang}}

{{&category.description2}}
{{/category.description2}}
```
## Complete Category Template Example ```mustache Complete Category Template theme={null}

{{category.name}}

{{#category.description1}}
{{&category.description1}}
{{/category.description1}}
{{#category.description2}}

{{#lang}}Additional Information{{/lang}}

{{&category.description2}}
{{/category.description2}}
```
## Usage Notes **Limited Category Object**: The category object only provides basic information (name and two description fields). Product listings, subcategories, pagination, and filtering functionality are not part of the category object itself and would need to be implemented through other means in the theme system. ## Best Practices Use description1 for main category content and description2 for additional information Use category names and descriptions effectively for search engine optimization Category descriptions support HTML content, use proper markup for structure Use semantic HTML and proper heading hierarchy for category content ## Next Steps Learn about individual product page implementation Build category navigation and breadcrumbs Optimize category pages for search engines Integrate shopping cart functionality # Order Object Source: https://quickbutik.dev/theme-development/pages/order Display order confirmation, thank you pages, and order details The `order` object contains information about completed orders and is available on order confirmation pages (thank you pages). It provides access to all order details including items, totals, shipping information, and customer data. **Usage**: Available on order confirmation/thank you pages - use to display order details and confirmation information ## Basic Order Information Access the core order details for confirmation pages: ```mustache Basic Order Confirmation theme={null}

{{#lang}}Thank you for your order!{{/lang}}

{{#lang}}Order number{{/lang}}: {{order.id}}

{{#lang}}We have received your order and will process it shortly.{{/lang}}

``` ```mustache Order Summary Card theme={null}

{{#lang}}Order Summary{{/lang}}

{{#lang}}Order Number{{/lang}}: {{order.id}}
{{#lang}}Total Amount{{/lang}}: {{order.value}} {{order.currency}}
```
## Available Properties ### Core Order Properties | Property | Type | Description | | ----------------------- | ------ | ---------------------------------------- | | `order.id` | String | Order number | | `order.value` | String | Order total amount incl. tax (formatted) | | `order.value_excl_tax` | String | Order total amount excl. tax (formatted) | | `order.tax_amount` | String | Tax amount on order (formatted) | | `order.currency` | String | Currency used for the order | | `order.shipping_amount` | String | Shipping cost (formatted) | | `order.shipping_name` | String | Shipping method chosen during order | | `order.payment_method` | String | Payment method used for the order | ### Customer Properties | Property | Type | Description | | ------------------------------ | ------ | ----------------------- | | `order.customer.email` | String | Customer email address | | `order.customer.firstname` | String | Customer first name | | `order.customer.lastname` | String | Customer last name | | `order.customer.ship_address` | String | Customer address Line 1 | | `order.customer.ship_address2` | String | Customer address Line 2 | | `order.customer.ship_zipcode` | String | Customer postal code | | `order.customer.ship_city` | String | Customer city | | `order.customer.ship_country` | String | Customer country | | `order.customer.ship_phone` | String | Customer phone number | ## Order Items Display the products that were purchased: ```mustache Basic Order Items theme={null}

{{#lang}}Items Ordered{{/lang}}

{{#order.items}}

{{title}}

{{#lang}}SKU{{/lang}}: {{sku}}

{{#variant}}

{{variant}}

{{/variant}}
{{price}} Γ— {{qty}}
{{/order.items}}
``` ```mustache Detailed Order Items Table theme={null}

{{#lang}}Order Details{{/lang}}

{{#order.items}} {{/order.items}} {{#order.value_excl_tax}} {{/order.value_excl_tax}} {{#order.shipping_amount}} {{/order.shipping_amount}} {{#order.tax_amount}} {{/order.tax_amount}}
{{#lang}}Product{{/lang}} {{#lang}}Price{{/lang}} {{#lang}}Quantity{{/lang}}

{{title}}

{{sku}}

{{#variant}}

{{variant}}

{{/variant}}
{{price}} {{qty}}
{{#lang}}Subtotal (excl. tax){{/lang}} {{order.value_excl_tax}}
{{#lang}}Shipping{{/lang}} ({{order.shipping_name}}) {{order.shipping_amount}}
{{#lang}}Tax{{/lang}} {{order.tax_amount}}
{{#lang}}Total{{/lang}} {{order.value}} {{order.currency}}
```
### Order Item Properties When looping through `order.items`, each item has these properties: | Property | Type | Description | | ------------ | ------ | ------------------------------------- | | `product_id` | String | Product ID | | `title` | String | Product title | | `sku` | String | Product/variant article number | | `variant_id` | String | Variant ID | | `variant` | String | Variant designation | | `price` | String | Product unit price (formatted) | | `price_raw` | String | Product unit price without formatting | | `qty` | Number | Quantity ordered of the product | ## Shipping Information Display shipping address using the available customer properties: ```mustache Shipping Details theme={null}

{{#lang}}Shipping Address{{/lang}}

{{order.customer.firstname}} {{order.customer.lastname}}

{{order.customer.ship_address}}

{{#order.customer.ship_address2}}

{{order.customer.ship_address2}}

{{/order.customer.ship_address2}}

{{order.customer.ship_zipcode}} {{order.customer.ship_city}}

{{order.customer.ship_country}}

{{#order.customer.ship_phone}}
{{order.customer.ship_phone}}
{{/order.customer.ship_phone}}
``` ```mustache Customer Information theme={null}

{{#lang}}Customer Information{{/lang}}

{{order.customer.firstname}} {{order.customer.lastname}}

{{order.customer.email}}

{{#order.customer.ship_phone}}

{{order.customer.ship_phone}}

{{/order.customer.ship_phone}}

{{#lang}}Shipping Address{{/lang}}

{{order.customer.ship_address}}

{{#order.customer.ship_address2}}

{{order.customer.ship_address2}}

{{/order.customer.ship_address2}}

{{order.customer.ship_zipcode}} {{order.customer.ship_city}}

{{order.customer.ship_country}}

```
## Payment Information Display payment method information: ```mustache Payment Details theme={null}

{{#lang}}Payment Information{{/lang}}

{{order.payment_method}}
```
## Customer Support Provide support information and next steps: ```mustache Customer Support theme={null}

{{#lang}}Need Help?{{/lang}}

{{#shop.phone}}

{{#lang}}Phone Support{{/lang}}

{{#lang}}Questions about your order?{{/lang}}

{{shop.phone}}
{{/shop.phone}}

{{#lang}}Please have your order number ready when contacting support{{/lang}}: {{order.id}}

```
## Complete Order Confirmation Example ```mustache Complete Order Confirmation Page theme={null}

{{#lang}}Thank you for your order!{{/lang}}

{{#lang}}Your order has been successfully placed and you will receive a confirmation email shortly.{{/lang}}

{{#lang}}Order Summary{{/lang}}

{{#lang}}Order{{/lang}} #{{order.id}}
{{#order.items}}

{{title}}

{{#variant}}

{{variant}}

{{/variant}}
{{price}} Γ— {{qty}}
{{/order.items}}
{{#order.value_excl_tax}}
{{#lang}}Subtotal (excl. tax){{/lang}} {{order.value_excl_tax}}
{{/order.value_excl_tax}} {{#order.shipping_amount}}
{{#lang}}Shipping{{/lang}} ({{order.shipping_name}}) {{order.shipping_amount}}
{{/order.shipping_amount}} {{#order.tax_amount}}
{{#lang}}Tax{{/lang}} {{order.tax_amount}}
{{/order.tax_amount}}
{{#lang}}Total{{/lang}} {{order.value}} {{order.currency}}

{{#lang}}Customer Information{{/lang}}

{{order.customer.firstname}} {{order.customer.lastname}}

{{order.customer.email}}

{{#order.customer.ship_phone}}

{{order.customer.ship_phone}}

{{/order.customer.ship_phone}}

{{#lang}}Shipping Address{{/lang}}

{{order.customer.ship_address}}

{{#order.customer.ship_address2}}

{{order.customer.ship_address2}}

{{/order.customer.ship_address2}}

{{order.customer.ship_zipcode}} {{order.customer.ship_city}}

{{order.customer.ship_country}}

{{#lang}}Payment & Shipping{{/lang}}

{{#lang}}Payment Method{{/lang}}: {{order.payment_method}}

{{#order.shipping_name}}

{{#lang}}Shipping Method{{/lang}}: {{order.shipping_name}}

{{/order.shipping_name}}

{{#lang}}What happens next?{{/lang}}

{{#lang}}You'll receive an email confirmation at{{/lang}} {{order.customer.email}}

{{#lang}}We'll process and pack your order{{/lang}}

{{#lang}}Your order will be shipped using{{/lang}} {{order.shipping_name}}

{{#lang}}Questions about your order?{{/lang}}

{{#lang}}Contact our customer service team with your order number{{/lang}}: {{order.id}}

{{#shop.phone}} {{shop.phone}} {{/shop.phone}}
{{#lang}}Continue Shopping{{/lang}}
```
## Best Practices Display all essential order information prominently and clearly Provide easy access to customer service with order number context Include order details that match the confirmation email format Ensure order confirmation works well on all devices ## Next Steps Learn about individual product page implementation Understand shopping cart and checkout integration Handle customer account and login functionality Optimize confirmation pages for search engines # Product Object Source: https://quickbutik.dev/theme-development/pages/product Display individual product information, images, variants, and purchase options The `product` object contains all information about a single product. It's available on product pages and provides access to everything needed to display product details, handle variants, and enable purchasing. **Usage**: Available on product pages - use to display product information, images, pricing, variants, and purchase forms ## Basic Product Information Access the core product details that every product page needs: ```mustache Basic Product Display theme={null}

{{product.title}}

{{#lang}}SKU{{/lang}}: {{product.sku}}

{{#product.gtin}}

{{#lang}}EAN{{/lang}}: {{product.gtin}}

{{/product.gtin}} {{#product.supplier_name}}

{{#lang}}Supplier{{/lang}}: {{product.supplier_name}}

{{/product.supplier_name}}
{{#product.has_before_price}} {{product.before_price}} {{/product.has_before_price}} {{product.price}} {{product.currency}}
{{&product.description}}
``` ```mustache Product Status theme={null}
{{#product.soldOut}} {{#lang}}Out of stock{{/lang}} {{/product.soldOut}} {{^product.soldOut}} {{#lang}}In stock{{/lang}} {{/product.soldOut}}
```
## Available Properties ### Core Properties | Property | Type | Description | | ----------------------- | ------ | ------------------------------- | | `product.id` | String | Product ID | | `product.sku` | String | Product article number | | `product.title` | String | Product title | | `product.description` | String | Product description | | `product.gtin` | String | Product EAN code | | `product.supplier_name` | String | Product supplier | | `product.supplier_sku` | String | Product supplier article number | ### Pricing Properties | Property | Type | Description | | -------------------------- | ------- | --------------------------------------- | | `product.price` | String | Product price (formatted) | | `product.price_raw` | String | Product price without formatting | | `product.before_price` | String | Product comparison price | | `product.has_before_price` | Boolean | Check if product has a comparison price | | `product.currency` | String | Currency | ### Stock Properties | Property | Type | Description | | ----------------- | ------- | -------------------------------- | | `product.soldOut` | Boolean | Check if product is out of stock | ### Custom Data Fields | Property | Type | Description | | --------------------- | ------ | ---------------------------------------------------------------------- | | `product.datafield_1` | String | Custom product information from data field 1 | | `product.datafield_2` | String | Custom product information from data field 2 | | `product.datafield_x` | String | Custom product information from data field X (replace X with field ID) | ## Product Images Display product images with responsive optimization: ```mustache Basic Image Gallery theme={null} ``` ```mustache Advanced Image Gallery theme={null} ``` ### Image Properties When looping through `product.images`: | Property | Type | Description | | ---------- | ------ | -------------- | | `image` | String | Image link | | `image_id` | String | Image ID | | `alttext` | String | Image ALT text | ### Special Image Properties | Property | Type | Description | | --------------------- | ------ | ---------------------- | | `product.firstimage` | String | Product's first image | | `product.secondimage` | String | Product's second image | ## Product Variants Handle product variants (size, color, etc.) and their selection: ```mustache Basic Variant Selection theme={null} {{#product.hasOptions}}

{{#lang}}Options{{/lang}}

{{#product.options}}
{{/product.options}}
{{/product.hasOptions}} ``` ```mustache Advanced Variant Display theme={null} {{#product.hasOptions}}
{{#product.options}}

{{option_title}}

{{#option_values}} {{/option_values}}
{{/product.options}}
{{/product.hasOptions}} ```
### Variant Properties When using `product.options`: | Property | Type | Description | | --------------- | ------ | ----------------------------------------------- | | `option_title` | String | Product option title | | `option_values` | Array | Object containing Product Option's all Variants | When looping through `option_values`: | Property | Type | Description | | -------- | ------ | ------------------- | | `id` | String | Variant ID | | `name` | String | Variant designation | ## Add to Cart Form Create functional purchase forms with variant support: ```mustache Basic Add to Cart theme={null} {{^product.soldOut}}
{{#product.hasOptions}} {{#product.options}} {{/product.options}} {{/product.hasOptions}}
{{/product.soldOut}} {{#product.soldOut}}
{{/product.soldOut}} ```
## Related Products Display related or recommended products using the related\_products object: ```mustache Related Products theme={null} {{#has_related_products}} {{/has_related_products}} ``` ### Related Product Properties When looping through `related_products`, access properties with `rp.` prefix: | Property | Type | Description | | --------------------- | ------- | ------------------------------------- | | `rp.title` | String | Product title | | `rp.id` | String | Product ID | | `rp.sku` | String | Product article number | | `rp.price` | String | Product price | | `rp.before_price` | String | Product comparison price | | `rp.has_before_price` | Boolean | Check if product has comparison price | | `rp.description` | String | Product description | | `rp.firstimage` | String | Product's first image | | `rp.secondimage` | String | Product's second image | | `rp.soldOut` | Boolean | Check if product is out of stock | | `rp.supplier_name` | String | Product supplier | | `rp.supplier_sku` | String | Product supplier article number | | `rp.hasOptions` | Boolean | Check if product has variants | ## Complete Product Page Example ```mustache Complete Product Page theme={null}
{{#product.firstimage}} {{product.title}} {{/product.firstimage}} {{^product.firstimage}}
{{#lang}}No image{{/lang}}
{{/product.firstimage}}
{{#product.images}}
{{#product.images}} {{/product.images}}
{{/product.images}} {{#product.secondimage}}
{{product.title}} - {{#lang}}Additional view{{/lang}}
{{/product.secondimage}}

{{product.title}}

{{#lang}}SKU{{/lang}}: {{product.sku}}

{{#product.gtin}}

{{#lang}}EAN{{/lang}}: {{product.gtin}}

{{/product.gtin}} {{#product.supplier_name}}

{{#lang}}Supplier{{/lang}}: {{product.supplier_name}}

{{/product.supplier_name}}
{{#product.has_before_price}} {{product.before_price}} {{#lang}}Sale{{/lang}} {{/product.has_before_price}} {{product.price}} {{product.currency}}
{{#product.soldOut}} {{#lang}}Out of stock{{/lang}} {{/product.soldOut}} {{^product.soldOut}} {{#lang}}In stock{{/lang}} {{/product.soldOut}}
{{#product.hasOptions}}
{{#product.options}}
{{/product.options}}
{{/product.hasOptions}} {{^product.soldOut}}
{{/product.soldOut}}

{{#lang}}Description{{/lang}}

{{&product.description}}
{{#product.datafield_1}}

{{#lang}}Additional Information{{/lang}}

{{product.datafield_1}}

{{/product.datafield_1}} {{#product.supplier_name}}

{{#lang}}Supplier Information{{/lang}}

{{#lang}}Supplier{{/lang}}: {{product.supplier_name}}

{{#product.supplier_sku}}

{{#lang}}Supplier SKU{{/lang}}: {{product.supplier_sku}}

{{/product.supplier_sku}}
{{/product.supplier_name}}
{{#has_related_products}} {{/has_related_products}}
```
## Best Practices Always use the `{{#img}}` wrapper with appropriate sizes for responsive images Include proper alt text, labels, and ARIA attributes for screen readers Use JavaScript to update pricing and availability when variants change Include structured data for products to enhance search results ## Next Steps Learn about displaying product listings and category pages Understand cart functionality and integration Display order confirmation and thank you pages Optimize product pages for search engines # Static Pages Source: https://quickbutik.dev/theme-development/pages/static Display static content pages using the basic page object Static pages are content pages that contain information about your store, policies, or other important details. These pages use a `page` object to access basic content. **Usage**: Available on static content pages - use to display basic page content ## Basic Page Information Access the core page content that is available: ```mustache Basic Static Page theme={null}
{{&page.content}}
``` ```mustache Static Page with Page Builder Check theme={null}

{{page.title}}

{{#page.qbuilder}}
{{&page.content}}
{{/page.qbuilder}} {{^page.qbuilder}}
{{&page.content}}
{{/page.qbuilder}}
```
## Available Properties ### Core Page Properties | Property | Type | Description | | --------------- | ------- | ----------------------------------------------------- | | `page.title` | String | Page title | | `page.content` | String | Main page content (HTML) | | `page.qbuilder` | Boolean | Check if Page Builder was used to create page content | ## Basic Page Template Create a simple static page layout: ```mustache Simple Page Layout theme={null}
{{&page.content}}
```
## Contact Page Template Create a basic contact page using available shop information: ```mustache Contact Page theme={null}

{{page.title}}

{{#lang}}Get in Touch{{/lang}}

{{&page.content}}
{{#shop.contact_text}}
{{&shop.contact_text}}
{{/shop.contact_text}} {{#shop.address}}

{{#lang}}Address{{/lang}}

{{shop.address}}

{{shop.zipcode}} {{shop.city}}

{{/shop.address}} {{#shop.phone}}

{{#lang}}Phone{{/lang}}

{{shop.phone}}

{{/shop.phone}}

{{#lang}}Send us a Message{{/lang}}

```
## About Page Template Create a basic about page: ```mustache About Page theme={null}

{{page.title}}

{{&page.content}}

{{#lang}}Ready to get started?{{/lang}}

{{#lang}}Browse our products and find what you're looking for{{/lang}}

{{#lang}}Shop Now{{/lang}}
```
## Legal Pages Template Create professional legal and policy pages: ```mustache Legal Page Template theme={null} ``` ## Complete Static Page Example ```mustache Generic Static Page theme={null} {{page.title}} - {{shop.name}}
{{&page.content}}
```
## Usage Notes **Limited Page Object**: The page object only provides basic information (title, content, and qbuilder status). Advanced features like SEO properties, featured images, excerpts, publishing dates, and custom settings are not available. ## Best Practices Use proper HTML structure within page content for organization Use page.qbuilder to handle different content rendering approaches Page content supports HTML, use semantic markup for accessibility Build contact forms using basic form elements and available shop properties ## Common Static Page Types | Page Type | Purpose | Available Elements | | ---------------- | --------------------------- | -------------------------------- | | About | Company information | Basic content, shop name/address | | Contact | Contact information | Contact form, shop address/phone | | FAQ | Frequently asked questions | Basic content structure | | Privacy Policy | Data protection information | Legal content | | Terms of Service | Usage terms and conditions | Legal content | | Shipping Info | Delivery information | Policy content | | Returns | Return policy | Process and contact info | ## Next Steps Build static page navigation and breadcrumbs Learn about available shop information properties Basic SEO optimization for static pages Handle contact form responses # Quickstart Tutorial Source: https://quickbutik.dev/theme-development/quickstart Build your first custom Quickbutik theme template in 15 minutes This tutorial will walk you through creating your first custom theme template. You'll learn the core concepts by building a simple product page layout with dynamic content. **Prerequisites**: Basic knowledge of HTML and CSS. Access to your Quickbutik Control Panel. ## What we'll build By the end of this tutorial, you'll have created a custom product page that: * βœ… Displays product information dynamically * βœ… Shows product images in a gallery * βœ… Handles product variants and stock status * βœ… Includes an add-to-cart button ## Step 1: Access the Theme Editor First, let's access your theme files: Log into your Quickbutik store and navigate to **Appearance β†’ Theme** Click **"Under the hood"** to open the theme code editor Look for the `product.html` file in the file list - this controls how individual product pages are displayed ## Step 2: Understanding the Basic Structure Let's start with a simple product page template: ```mustache product.html theme={null} {{product.title}} - {{shop.name}}
``` * `{{product.title}}` - Displays the product name * `{{shop.name}}` - Shows your store name * `{{seo.description}}` - SEO-optimized description for the page ## Step 3: Add Product Information Now let's add the core product information: ```mustache Basic Product Info theme={null}

{{product.title}}

{{#product.has_before_price}} {{product.before_price}} {{/product.has_before_price}} {{product.price}} {{product.currency}}
{{&product.description}}
``` ```mustache Stock Status theme={null}
{{#product.soldOut}} {{#lang}}Out of stock{{/lang}} {{/product.soldOut}} {{^product.soldOut}} {{#lang}}In stock{{/lang}} {{/product.soldOut}}
```
### What's happening here? We show both regular and sale prices, with the `has_before_price` conditional showing sale pricing only when relevant. Using `#` and `^` operators to show different content based on whether the product is sold out. The `{{#lang}}` wrapper automatically translates text based on your store's language settings. The `&` symbol in `{{&product.description}}` renders HTML content without escaping it. ## Step 4: Product Image Gallery Let's create a dynamic image gallery: ```mustache Image Gallery theme={null} ``` The `{{#img}}` wrapper automatically optimizes images. The `_800x600` suffix resizes the image to 800x600 pixels. ## Step 5: Product Variants If your product has variants (like size or color), let's display them: ```mustache Product Variants theme={null} {{#product.hasOptions}}

{{#lang}}Options{{/lang}}

{{#product.options}}
{{/product.options}}
{{/product.hasOptions}} ``` ## Step 6: Add to Cart Button Finally, let's add a functional add-to-cart button: ```mustache Add to Cart theme={null}
{{^product.soldOut}}
{{#product.hasOptions}}
{{/product.hasOptions}}
{{/product.soldOut}} {{#product.soldOut}} {{/product.soldOut}}
``` ## Step 7: Complete Example Here's your complete product page template: ```mustache Complete Template theme={null} {{product.title}} - {{shop.name}}

{{product.title}}

{{#product.has_before_price}} {{product.before_price}} {{/product.has_before_price}} {{product.price}} {{product.currency}}
{{#product.soldOut}} {{#lang}}Out of stock{{/lang}} {{/product.soldOut}} {{^product.soldOut}} {{#lang}}In stock{{/lang}} {{/product.soldOut}}
{{&product.description}}
{{#product.hasOptions}}
{{#product.options}}
{{/product.options}}
{{/product.hasOptions}}
{{^product.soldOut}}
{{/product.soldOut}} {{#product.soldOut}} {{/product.soldOut}}
```
## Step 8: Test Your Template Click **Save** in the theme editor Use the preview function to see your changes on a test product * Products with/without images * Products with/without variants * Products that are in stock vs. sold out When you're satisfied, publish your changes to make them live ## πŸŽ‰ Congratulations! You've successfully created your first custom Quickbutik theme template! You now understand: * βœ… How to display product data dynamically * βœ… How to use conditionals for different states * βœ… How to handle product variants and options * βœ… How to create functional form elements ## Next Steps Learn more about the templating language fundamentals Explore store-wide data like navigation, cart, and settings Learn about dynamic elements and complex layouts Discover performance tips and coding standards ## Common Issues Make sure you clicked **Save** and try clearing your browser cache. Some changes may take a few minutes to appear. Check that you're viewing the template on a product page. Some data is only available on specific page types. Remember that your theme's CSS file controls the visual appearance. The template only handles the HTML structure and dynamic content. # Template Structure Source: https://quickbutik.dev/theme-development/template-structure Understanding Quickbutik theme architecture and file organization # Template Structure Quickbutik themes are built using the **Mustache** templating language. This guide covers the theme architecture and file organization you need to understand before building your first theme. ## Theme Architecture ### Template Files Quickbutik themes consist of several template files, each serving a specific purpose: | Template File | Purpose | Usage | | ----------------- | ----------------------------- | ---------------------------- | | **Startpage** | Homepage/Landing page | Main store entry point | | **Produktsida** | Product detail page | Individual product display | | **Produktlista** | Product listing/Category page | Product collection views | | **InnehΓ₯llssida** | Static content pages | About, Terms, Custom pages | | **Kontaktsida** | Contact page | Contact form and information | | **Blog** | Blog overview | Blog post listings | | **Blog\_post** | Individual blog post | Single blog post display | | **Tack-sida** | Thank you page | Order confirmation | ### Accessing Template Files You can access and edit your theme's source code through your Quickbutik control panel: **Utseende > Tema > Kod under huven** Always make a backup of your theme before making changes, and test thoroughly in a theme under construction before publishing to your live version! ## Data Availability Understanding which data is available on which templates is crucial for effective theme development: ### Global Objects Available on **all pages**: * `shop` - Store settings and configuration * `user` - Customer login/account information * `basket` - Shopping cart data * `seo` - Search engine optimization data * `linklist` - Navigation menus * `paylink` - Checkout URL ### Page-Specific Objects Available only on **specific templates**: | Object | Available On | Purpose | | ----------------------- | ------------------------- | --------------------------------- | | `product` | Produktsida, Produktlista | Product information | | `products` | Produktlista | Product collection for categories | | `category` | Produktlista | Category information | | `order` | Tack-sida | Order confirmation details | | `blog`, `posts`, `post` | Blog pages | Blog content and navigation | | `page` | InnehΓ₯llssida | Static page content | | `response_data` | Kontaktsida | Contact form responses | ## File Organization Best Practices ### CSS and Assets ``` /css/ styles.css - Main stylesheet responsive.css - Mobile/tablet styles /js/ theme.js - Theme functionality /images/ logo.png - Store logo placeholders/ - Default images ``` ### Template Structure Example ```mustache theme={null} {{seo.title}} - {{shop.name}}

{{shop.name}}

{{#linklist.main}} {{name}} {{/linklist.main}}
``` ## Template Inheritance Patterns ### Common Header/Footer Most themes follow this pattern: 1. **Header section** - Logo, navigation, search (same across all pages) 2. **Main content** - Varies by template type 3. **Footer section** - Links, contact info (same across all pages) ### Responsive Considerations Structure your templates with mobile-first approach: * Use flexible grid systems * Optimize images with `{{#img}}` wrapper * Test on multiple screen sizes ## Next Steps Now that you understand the theme structure, learn the templating language: Learn the fundamentals of Mustache templating How to access and display data in your themes Explore shop, user, and navigation data Set up your development environment # Wrappers and Functions Source: https://quickbutik.dev/theme-development/wrappers-and-functions Master helper functions, data wrappers, and content manipulation in Quickbutik themes Wrappers and functions in Quickbutik's Mustache implementation provide essential tools for transforming and manipulating data. These helper functions extend Mustache's capabilities for specific use cases like image optimization, translations, and asset linking. **Prerequisites**: This guide covers the available wrapper functions. Make sure you understand [basic Mustache syntax](/theme-development/mustache-basics) and [object access patterns](/theme-development/objects-and-attributes) first. **Key Concept**: Wrappers use the `{{#function}}content{{/function}}` syntax to process and transform data ## Available Wrappers According to the Quickbutik platform, only the following wrappers are supported: | Wrapper | Purpose | Usage | | ------------------ | -------------------------------------- | ------------------------------------------------------------------------- | | `{{#img}}` | Image manipulation and optimization | `{{#img}}{{product.firstimage}}_400x400{{/img}}` | | `{{#lang}}` | Text translation via Control Panel | `{{#lang}}My text here{{/lang}}` | | `{{#assets}}` | Link to correct path for specific file | `{{#assets}}css/style.css{{/assets}}` | | `{{#breadcrumbs}}` | Output breadcrumbs/category structure | `{{#breadcrumbs}} {{url}} {{title}} {{^last}} {{/last}} {{/breadcrumbs}}` | | `{{#asset_flags}}` | Return flag icon for language | `{{#asset_flags}}{{id}}{{/asset_flags}}` | ## Image Wrapper (`{{#img}}`) The image wrapper is essential for handling product images and media optimization: ```mustache Basic Image Resizing theme={null} {{product.title}} {{product.title}} {{product.title}} ``` ```mustache Product Image Gallery theme={null} {{#product.images}}
{{alttext}}
{{/product.images}} ``` ```mustache Responsive Images theme={null} {{product.title}} ```
### Image Size Options | Size Format | Description | Use Case | | ----------- | ---------------- | ---------------------------- | | `_100x100` | Square thumbnail | Product grid thumbnails | | `_300x200` | Landscape card | Product cards, lists | | `_400x400` | Medium square | Product detail thumbnails | | `_800x600` | Large landscape | Product detail main image | | `_1200x800` | Extra large | Lightbox, zoom functionality | ### Settings Home Elements Images ```mustache Home Elements with Images theme={null} {{#settings.home_elements}} {{#element}} {{#image1_link}}
{{title}}
{{/image1_link}} {{#use_image2}} {{#image2_link}}
{{title}}
{{/image2_link}} {{/use_image2}} {{/element}} {{/settings.home_elements}} ```
## Language Wrapper (`{{#lang}}`) Handle multi-language content and translations via the Control Panel: ```mustache Basic Translation theme={null}

{{#lang}}Welcome to our store{{/lang}}

{{#lang}}Free shipping on orders over $50{{/lang}}

``` ```mustache Dynamic Content Translation theme={null}
{{#product.soldOut}} {{#lang}}Out of Stock{{/lang}} {{/product.soldOut}} {{^product.soldOut}} {{#lang}}In Stock{{/lang}} {{/product.soldOut}}
{{#basket.isEmpty}}

{{#lang}}Your cart is empty{{/lang}}

{{/basket.isEmpty}} {{^basket.isEmpty}}

{{basket.items_count}} {{#lang}}items in cart{{/lang}}

{{/basket.isEmpty}}
``` ```mustache Form Labels and Messages theme={null}
```
### Navigation and UI Translation ```mustache Navigation Translation theme={null}
{{#shop.login_active}} {{#user.logged_in}} {{#lang}}My Account{{/lang}} {{/user.logged_in}} {{^user.logged_in}} {{#lang}}Log In{{/lang}} {{/user.logged_in}} {{/shop.login_active}}
```
## Assets Wrapper (`{{#assets}}`) Link to correct paths for theme files like CSS, JavaScript, and other assets: ```mustache CSS and JavaScript Assets theme={null} ``` ```mustache Image and Font Assets theme={null} {{shop.name}} {{#lang}}No image available{{/lang}} ``` ```mustache Icon and Resource Assets theme={null} ``` ## Breadcrumbs Wrapper (`{{#breadcrumbs}}`) Output breadcrumbs/category structure regardless of the current page: ```mustache Basic Breadcrumbs theme={null} ``` ```mustache Styled Breadcrumbs theme={null} ``` ```mustache Breadcrumbs with Icons theme={null} ``` ### Breadcrumb Properties Within the `{{#breadcrumbs}}` loop: | Property | Type | Description | | -------- | ------- | -------------------------------------------------- | | `url` | String | Link URL for the breadcrumb item | | `title` | String | Display text for the breadcrumb item | | `last` | Boolean | True if this is the last (current) breadcrumb item | ## Asset Flags Wrapper (`{{#asset_flags}}`) Return flag icons for languages (used within the languages loop): ```mustache Language Selector with Flags theme={null} {{#shop.app.languages}}

{{#lang}}Choose Language{{/lang}}

{{#shop.languages}} {{id}} {{id}} {{/shop.languages}}
{{/shop.app.languages}} ``` ```mustache Compact Language Switcher theme={null} {{#shop.app.languages}}
{{#shop.languages}} {{id}} {{/shop.languages}}
{{/shop.app.languages}} ``` ```mustache Language Dropdown theme={null} {{#shop.app.languages}}
{{#shop.languages}} {{id}} {{id}} {{/shop.languages}}
{{/shop.app.languages}} ```
## Combining Wrappers ### Complete Examples with Multiple Wrappers ```mustache Product Card with All Wrappers theme={null}
{{#product.firstimage}} {{product.title}} {{/product.firstimage}} {{^product.firstimage}} {{#lang}}No image available{{/lang}} {{/product.firstimage}}

{{product.title}}

{{product.price}}

{{#product.soldOut}} {{#lang}}Out of Stock{{/lang}} {{/product.soldOut}} {{^product.soldOut}} {{/product.soldOut}}
``` ```mustache Complete Page Template theme={null} {{seo.title}}
```
## Best Practices Always use the `{{#img}}` wrapper with appropriate dimensions for optimal performance Wrap all user-facing text with `{{#lang}}` for proper multi-language support Use `{{#assets}}` for all theme files to ensure correct paths across environments Implement breadcrumbs for better user navigation and SEO benefits ## Common Patterns ### Error Handling and Fallbacks ```mustache Image Fallbacks theme={null} {{#product.firstimage}} {{product.title}} {{/product.firstimage}} {{^product.firstimage}} {{#lang}}No image available{{/lang}} {{/product.firstimage}} ``` ```mustache Content Fallbacks theme={null} {{#shop.contact_text}}
{{&shop.contact_text}}
{{/shop.contact_text}} {{^shop.contact_text}}

{{#lang}}Contact information not available{{/lang}}

{{/shop.contact_text}} ```
## Next Steps Learn how to use wrappers with global object data Apply wrappers in product page templates Understand how wrappers fit into overall theme architecture Follow coding standards and optimization guidelines # Events Source: https://quickbutik.dev/webhooks/events Available Events: **order** * event\_type: order.new > New order (paid). Sent with parameter `order_id`. * event\_type: order.done > Order marked as done (sent). Sent with parameter `order_id`. * event\_type: order.cancelled > Order marked as cancelled. Sent with parameter `order_id`. **product** * event\_type: product.add > Product added. Sent with parameter `product_id`. * event\_type: product.update > Product updated. Sent with parameter `product_id`. * event\_type: product.delete > Product deleted. Sent with parameter `product_id`. # Webhooks Source: https://quickbutik.dev/webhooks/introduction Get real-time notifications when events occur in your Quickbutik store Webhooks allow your application to receive real-time notifications when important events happen in your Quickbutik store. Instead of constantly polling the API, webhooks push data to your application immediately when events occur. **Common use cases:** * Sync new orders to your fulfillment system instantly * Update inventory levels across multiple platforms * Send custom order confirmations or notifications * Trigger automated workflows and integrations ## How Webhooks Work When an event occurs in your store (like a new order), Quickbutik sends an HTTP GET request to your configured webhook URL with event details as query parameters. ```mermaid theme={null} sequenceDiagram participant QS as Quickbutik Store participant QW as Quickbutik Webhooks participant YE as Your Endpoint participant YS as Your System QS->>QW: Order Created QW->>YE: GET /webhook?event_type=order.new&order_id=12345 YE->>YE: Acknowledge (200 OK) YE->>YS: Process Order Async YS->>QS: Fetch Order Details (API) ``` ## Quick Setup Set up an endpoint in your application to receive webhook notifications ```javascript theme={null} app.get('/webhooks/quickbutik', (req, res) => { const { event_type, order_id, product_id } = req.query; // Always respond quickly res.status(200).send('OK'); // Process webhook asynchronously processWebhook(event_type, { order_id, product_id }); }); ``` Enable webhooks in your Quickbutik Control Panel under **Settings β†’ Webhooks** and add your endpoint URL Process the webhook events in your application based on the event type ## Event Structure All webhook requests are sent as GET requests with the following query parameters: | Parameter | Description | Example | | ------------ | ------------------------------- | ----------------------------- | | `event_type` | The type of event that occurred | `order.new`, `product.update` | | `order_id` | Order ID (for order events) | `12345` | | `product_id` | Product ID (for product events) | `67890` | ### Example Webhook Request ``` GET /your-webhook-endpoint?event_type=order.new&order_id=12345 Host: your-domain.com User-Agent: Quickbutik-Webhooks/1.0 ``` ## Complete Example Here's a complete webhook handler that processes different event types: ```javascript Express.js theme={null} const express = require('express'); const QuickbutikAPI = require('./quickbutik-api'); const app = express(); const api = new QuickbutikAPI(process.env.QUICKBUTIK_API_KEY); app.get('/webhooks/quickbutik', async (req, res) => { const { event_type, order_id, product_id } = req.query; console.log(`Received webhook: ${event_type}`, { order_id, product_id }); // Acknowledge webhook immediately (important!) res.status(200).send('OK'); try { // Process webhook asynchronously switch (event_type) { case 'order.new': await handleNewOrder(order_id); break; case 'order.done': await handleOrderShipped(order_id); break; case 'order.cancelled': await handleOrderCancelled(order_id); break; case 'product.add': await handleProductAdded(product_id); break; case 'product.update': await handleProductUpdated(product_id); break; case 'product.delete': await handleProductDeleted(product_id); break; default: console.log(`Unhandled event type: ${event_type}`); } } catch (error) { console.error(`Error processing webhook ${event_type}:`, error); // In production, add to retry queue or send alert await handleWebhookError(event_type, { order_id, product_id }, error); } }); async function handleNewOrder(orderId) { console.log(`Processing new order: ${orderId}`); // Fetch complete order details const orders = await api.getOrders({ order_id: orderId, include_details: true }); if (orders && orders.length > 0) { const order = orders[0]; // Your business logic here await processNewOrder(order); console.log(`Successfully processed order ${orderId}`); } } async function handleProductUpdated(productId) { console.log(`Processing product update: ${productId}`); // Fetch updated product details const products = await api.getProducts({ product_id: productId, include_details: true }); if (products && products.length > 0) { const product = products[0]; // Your business logic here await syncProductToExternalSystem(product); console.log(`Successfully synced product ${productId}`); } } app.listen(3000, () => { console.log('Webhook server running on port 3000'); }); ``` ```python Flask theme={null} from flask import Flask, request, jsonify import asyncio import logging from quickbutik_api import QuickbutikAPI app = Flask(__name__) logging.basicConfig(level=logging.INFO) api = QuickbutikAPI(os.getenv('QUICKBUTIK_API_KEY')) @app.route('/webhooks/quickbutik', methods=['GET']) def quickbutik_webhook(): event_type = request.args.get('event_type') order_id = request.args.get('order_id') product_id = request.args.get('product_id') app.logger.info(f'Received webhook: {event_type}, order_id: {order_id}, product_id: {product_id}') # Acknowledge webhook immediately (important!) response = jsonify({'status': 'received'}) # Process webhook asynchronously try: if event_type == 'order.new': asyncio.create_task(handle_new_order(order_id)) elif event_type == 'order.done': asyncio.create_task(handle_order_shipped(order_id)) elif event_type == 'order.cancelled': asyncio.create_task(handle_order_cancelled(order_id)) elif event_type == 'product.add': asyncio.create_task(handle_product_added(product_id)) elif event_type == 'product.update': asyncio.create_task(handle_product_updated(product_id)) elif event_type == 'product.delete': asyncio.create_task(handle_product_deleted(product_id)) else: app.logger.info(f'Unhandled event type: {event_type}') except Exception as error: app.logger.error(f'Error processing webhook {event_type}: {error}') asyncio.create_task(handle_webhook_error(event_type, {'order_id': order_id, 'product_id': product_id}, error)) return response, 200 async def handle_new_order(order_id): app.logger.info(f'Processing new order: {order_id}') try: # Fetch complete order details orders = await api.get_orders(order_id=order_id, include_details=True) if orders: order = orders[0] # Your business logic here await process_new_order(order) app.logger.info(f'Successfully processed order {order_id}') except Exception as error: app.logger.error(f'Failed to process order {order_id}: {error}') async def handle_product_updated(product_id): app.logger.info(f'Processing product update: {product_id}') try: # Fetch updated product details products = await api.get_products(product_id=product_id, include_details=True) if products: product = products[0] # Your business logic here await sync_product_to_external_system(product) app.logger.info(f'Successfully synced product {product_id}') except Exception as error: app.logger.error(f'Failed to sync product {product_id}: {error}') if __name__ == '__main__': app.run(debug=True, port=3000) ``` ## Best Practices **Always respond with 200 OK within 10 seconds** to acknowledge receipt. Process the actual work asynchronously. **Webhooks may be sent multiple times** if your endpoint doesn't respond. Make your processing idempotent. **Verify webhook authenticity** in production environments using request validation. **Implement proper error handling** and retry mechanisms for failed webhook processing. **Important Considerations:** * Webhooks are sent as GET requests (not POST) * Always acknowledge webhooks quickly to avoid retries * Events may be delivered more than once - ensure idempotent processing * Use the API to fetch complete data when processing webhooks ## Testing Webhooks Use tools like ngrok to test webhooks locally: ```bash Terminal theme={null} # Install ngrok npm install -g ngrok # Start your webhook server node webhook-server.js # In another terminal, expose your local server ngrok http 3000 # Use the ngrok URL in your Quickbutik webhook settings # Example: https://abc123.ngrok.io/webhooks/quickbutik ``` ```javascript Testing Script theme={null} // Simple webhook tester const express = require('express'); const app = express(); app.get('/webhooks/quickbutik', (req, res) => { console.log('Webhook received:', { event_type: req.query.event_type, order_id: req.query.order_id, product_id: req.query.product_id, timestamp: new Date().toISOString() }); res.status(200).send('Webhook received successfully'); }); app.listen(3000, () => { console.log('Webhook test server running on http://localhost:3000'); console.log('Test URL: http://localhost:3000/webhooks/quickbutik'); }); ``` ## Next Steps Explore all available webhook events and their data Detailed webhook configuration and troubleshooting # Webhook Setup Source: https://quickbutik.dev/webhooks/setup Configure and troubleshoot your webhook endpoints This guide walks you through setting up webhooks in your Quickbutik store and configuring your application to receive and process webhook events reliably. ## Configuration in Quickbutik Log into your Quickbutik Control Panel and navigate to **Settings β†’ Webhooks** Enter your webhook endpoint URL where you want to receive notifications: ``` https://your-domain.com/webhooks/quickbutik ``` Your webhook URL must be publicly accessible and respond to GET requests. Use HTTPS in production for security. Choose which events should trigger webhook notifications: * **Order events**: `order.new`, `order.done`, `order.cancelled` * **Product events**: `product.add`, `product.update`, `product.delete` Use the test function in the Quickbutik panel to verify your endpoint is working correctly ## Development Setup For local development, use a tool like ngrok to expose your local server: ```bash Terminal theme={null} # Install ngrok npm install -g ngrok # Start your local webhook server node webhook-server.js # In another terminal, expose your local server ngrok http 3000 # Copy the ngrok URL to your Quickbutik webhook settings # Example: https://abc123.ngrok.io/webhooks/quickbutik ``` ```javascript Local Server theme={null} const express = require('express'); const app = express(); app.get('/webhooks/quickbutik', (req, res) => { console.log('Webhook received:', { event_type: req.query.event_type, order_id: req.query.order_id, product_id: req.query.product_id, timestamp: new Date().toISOString(), ip: req.ip, headers: req.headers }); res.status(200).send('OK'); }); app.listen(3000, () => { console.log('Webhook server listening on port 3000'); }); ``` ## Production Deployment For production environments, ensure your webhook endpoint is robust and scalable: ```javascript Production Webhook theme={null} const express = require('express'); const rateLimit = require('express-rate-limit'); const helmet = require('helmet'); const app = express(); // Security middleware app.use(helmet()); // Rate limiting const webhookLimiter = rateLimit({ windowMs: 1 * 60 * 1000, // 1 minute max: 100, // limit each IP to 100 requests per windowMs message: 'Too many webhook requests from this IP', standardHeaders: true, legacyHeaders: false, }); app.use('/webhooks', webhookLimiter); // Health check endpoint app.get('/health', (req, res) => { res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() }); }); // Webhook endpoint app.get('/webhooks/quickbutik', async (req, res) => { const { event_type, order_id, product_id } = req.query; // Log webhook receipt console.log(`Webhook received: ${event_type}`, { order_id, product_id, timestamp: new Date().toISOString(), ip: req.ip }); // Respond immediately res.status(200).send('OK'); try { // Add to processing queue await addToQueue({ event_type, order_id, product_id, received_at: new Date().toISOString() }); } catch (error) { console.error('Failed to queue webhook:', error); // Send alert but don't fail the webhook response await sendAlert({ type: 'webhook_queue_error', event_type, error: error.message }); } }); async function addToQueue(webhookData) { // Add to Redis queue, SQS, or your preferred queue system // This ensures webhook processing doesn't block the response if (process.env.REDIS_URL) { const redis = require('redis'); const client = redis.createClient({ url: process.env.REDIS_URL }); await client.lPush('webhook_queue', JSON.stringify(webhookData)); } } const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Webhook server running on port ${PORT}`); }); ``` ```dockerfile Dockerfile theme={null} FROM node:18-alpine WORKDIR /app # Install dependencies COPY package*.json ./ RUN npm ci --only=production # Copy application COPY . . # Create non-root user RUN addgroup -g 1001 -S webhook RUN adduser -S webhook -u 1001 # Security RUN chown -R webhook:webhook /app USER webhook # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:3000/health || exit 1 EXPOSE 3000 CMD ["node", "webhook-server.js"] ``` ## Troubleshooting ### Common Issues **Possible causes:** * Webhook URL is not publicly accessible * Firewall blocking incoming requests * Server not running or crashed * Wrong endpoint path configured **Solutions:** * Test your endpoint with curl: `curl "https://your-domain.com/webhooks/quickbutik?event_type=test"` * Check server logs for errors * Verify webhook URL in Quickbutik settings * Use tools like ngrok for local testing **Possible causes:** * Webhook handler takes too long to respond * Processing heavy operations synchronously * Database or external API calls blocking response **Solutions:** * Always respond with 200 OK immediately * Process webhook data asynchronously * Use queue systems for heavy processing * Implement timeout handling **Possible causes:** * Webhook endpoint responding slowly or with errors * Network issues causing retries * Processing failures triggering redelivery **Solutions:** * Implement idempotent processing * Store processed webhook IDs to prevent duplicates * Respond quickly with 200 OK * Use database transactions for atomic operations **Possible causes:** * Endpoint was down during event * Processing errors causing webhook to be marked as failed * Rate limiting rejecting webhooks **Solutions:** * Implement reliable webhook processing with retries * Monitor webhook endpoint uptime * Use dead letter queues for failed webhooks * Regularly poll API for missed events as backup ## Next Steps Explore all available webhook events and their data