install
source · Clone the upstream repo
git clone https://github.com/alinaqi/claude-bootstrap
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/alinaqi/claude-bootstrap "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/woocommerce" ~/.claude/skills/alinaqi-claude-bootstrap-woocommerce && rm -rf "$T"
manifest:
skills/woocommerce/SKILL.mdsource content
WooCommerce Development Skill
For integrating with WooCommerce stores via REST API - products, orders, customers, webhooks, and custom extensions.
Sources: WooCommerce REST API | Developer Docs
Prerequisites
Store Requirements
# WooCommerce store must have: # 1. WordPress with WooCommerce plugin installed # 2. HTTPS enabled (required for API auth) # 3. Permalinks set to anything except "Plain" # WordPress Admin → Settings → Permalinks → Post name (recommended)
Generate API Keys
- Go to WooCommerce → Settings → Advanced → REST API
- Click Add key
- Set Description, User (admin), and Permissions (Read/Write)
- Click Generate API key
- Copy Consumer Key and Consumer Secret (shown only once)
API Basics
Base URL
https://your-store.com/wp-json/wc/v3/
Authentication
// Node.js - Basic Auth (recommended) const WooCommerceRestApi = require("@woocommerce/woocommerce-rest-api").default; const api = new WooCommerceRestApi({ url: "https://your-store.com", consumerKey: process.env.WC_CONSUMER_KEY, consumerSecret: process.env.WC_CONSUMER_SECRET, version: "wc/v3" });
# Python from woocommerce import API wcapi = API( url="https://your-store.com", consumer_key=os.environ["WC_CONSUMER_KEY"], consumer_secret=os.environ["WC_CONSUMER_SECRET"], version="wc/v3" )
Query String Auth (Fallback)
# Only use if Basic Auth fails (some hosting configurations) curl https://your-store.com/wp-json/wc/v3/products \ ?consumer_key=ck_xxx&consumer_secret=cs_xxx
Installation
Node.js
npm install @woocommerce/woocommerce-rest-api
// lib/woocommerce.ts import WooCommerceRestApi from "@woocommerce/woocommerce-rest-api"; const api = new WooCommerceRestApi({ url: process.env.WC_STORE_URL!, consumerKey: process.env.WC_CONSUMER_KEY!, consumerSecret: process.env.WC_CONSUMER_SECRET!, version: "wc/v3", queryStringAuth: false, // Set true for HTTP (dev only) }); export default api;
Python
pip install woocommerce
# lib/woocommerce.py import os from woocommerce import API wcapi = API( url=os.environ["WC_STORE_URL"], consumer_key=os.environ["WC_CONSUMER_KEY"], consumer_secret=os.environ["WC_CONSUMER_SECRET"], version="wc/v3", timeout=30 )
Products
List Products
// Node.js async function getProducts(page = 1, perPage = 20) { const response = await api.get("products", { page, per_page: perPage, status: "publish", }); return response.data; } // With filters async function searchProducts(search: string, category?: number) { const response = await api.get("products", { search, category: category || undefined, orderby: "popularity", order: "desc", }); return response.data; }
# Python def get_products(page=1, per_page=20): response = wcapi.get("products", params={ "page": page, "per_page": per_page, "status": "publish" }) return response.json()
Get Single Product
async function getProduct(productId: number) { const response = await api.get(`products/${productId}`); return response.data; }
Create Product
async function createProduct(data: ProductInput) { const response = await api.post("products", { name: data.name, type: "simple", // simple, variable, grouped, external regular_price: data.price.toString(), description: data.description, short_description: data.shortDescription, categories: data.categoryIds.map(id => ({ id })), images: data.images.map(url => ({ src: url })), manage_stock: true, stock_quantity: data.stockQuantity, status: "publish", }); return response.data; }
Update Product
async function updateProduct(productId: number, data: Partial<ProductInput>) { const response = await api.put(`products/${productId}`, data); return response.data; } // Update stock only async function updateStock(productId: number, quantity: number) { const response = await api.put(`products/${productId}`, { stock_quantity: quantity, }); return response.data; }
Delete Product
async function deleteProduct(productId: number, force = false) { // force: true = permanent delete, false = move to trash const response = await api.delete(`products/${productId}`, { force, }); return response.data; }
Variable Products
// Create variable product async function createVariableProduct(data: VariableProductInput) { // 1. Create product with type "variable" const product = await api.post("products", { name: data.name, type: "variable", attributes: [ { name: "Size", visible: true, variation: true, options: ["Small", "Medium", "Large"], }, { name: "Color", visible: true, variation: true, options: ["Red", "Blue"], }, ], }); // 2. Create variations for (const variant of data.variants) { await api.post(`products/${product.data.id}/variations`, { regular_price: variant.price.toString(), stock_quantity: variant.stock, attributes: [ { name: "Size", option: variant.size }, { name: "Color", option: variant.color }, ], }); } return product.data; } // Get variations async function getVariations(productId: number) { const response = await api.get(`products/${productId}/variations`); return response.data; }
Orders
List Orders
async function getOrders(params: OrderQueryParams = {}) { const response = await api.get("orders", { page: params.page || 1, per_page: params.perPage || 20, status: params.status || "any", // pending, processing, completed, etc. after: params.after, // ISO date string before: params.before, }); return response.data; } // Get recent orders async function getRecentOrders(days = 7) { const after = new Date(); after.setDate(after.getDate() - days); const response = await api.get("orders", { after: after.toISOString(), orderby: "date", order: "desc", }); return response.data; }
Get Single Order
async function getOrder(orderId: number) { const response = await api.get(`orders/${orderId}`); return response.data; }
Create Order
async function createOrder(data: OrderInput) { const response = await api.post("orders", { payment_method: "stripe", payment_method_title: "Credit Card", set_paid: false, billing: { first_name: data.customer.firstName, last_name: data.customer.lastName, email: data.customer.email, phone: data.customer.phone, address_1: data.billing.address1, city: data.billing.city, state: data.billing.state, postcode: data.billing.postcode, country: data.billing.country, }, shipping: { first_name: data.customer.firstName, last_name: data.customer.lastName, address_1: data.shipping.address1, city: data.shipping.city, state: data.shipping.state, postcode: data.shipping.postcode, country: data.shipping.country, }, line_items: data.items.map(item => ({ product_id: item.productId, variation_id: item.variationId, quantity: item.quantity, })), shipping_lines: [ { method_id: "flat_rate", method_title: "Flat Rate", total: data.shippingCost.toString(), }, ], }); return response.data; }
Update Order Status
async function updateOrderStatus(orderId: number, status: OrderStatus) { const response = await api.put(`orders/${orderId}`, { status, // pending, processing, on-hold, completed, cancelled, refunded, failed }); return response.data; } // Add order note async function addOrderNote(orderId: number, note: string, customerNote = false) { const response = await api.post(`orders/${orderId}/notes`, { note, customer_note: customerNote, // true = visible to customer }); return response.data; }
Order Statuses
| Status | Description |
|---|---|
| Awaiting payment |
| Payment received, awaiting fulfillment |
| Awaiting action (stock, payment confirmation) |
| Order fulfilled |
| Cancelled by admin or customer |
| Refunded |
| Payment failed |
Customers
List Customers
async function getCustomers(params: CustomerQueryParams = {}) { const response = await api.get("customers", { page: params.page || 1, per_page: params.perPage || 20, role: "customer", orderby: "registered_date", order: "desc", }); return response.data; } // Search customers async function searchCustomers(email: string) { const response = await api.get("customers", { email, }); return response.data; }
Create Customer
async function createCustomer(data: CustomerInput) { const response = await api.post("customers", { email: data.email, first_name: data.firstName, last_name: data.lastName, username: data.email.split("@")[0], billing: { first_name: data.firstName, last_name: data.lastName, email: data.email, phone: data.phone, address_1: data.address1, city: data.city, state: data.state, postcode: data.postcode, country: data.country, }, shipping: { // Same as billing or different }, }); return response.data; }
Update Customer
async function updateCustomer(customerId: number, data: Partial<CustomerInput>) { const response = await api.put(`customers/${customerId}`, data); return response.data; }
Webhooks
Create Webhook
async function createWebhook(topic: string, deliveryUrl: string) { const response = await api.post("webhooks", { name: `Webhook for ${topic}`, topic, // order.created, order.updated, product.created, etc. delivery_url: deliveryUrl, status: "active", secret: process.env.WC_WEBHOOK_SECRET, }); return response.data; }
Webhook Topics
| Topic | Trigger |
|---|---|
| New order placed |
| Order status/details changed |
| Order deleted |
| New product created |
| Product updated |
| Product deleted |
| New customer registered |
| Customer updated |
| New coupon created |
Verify Webhook Signature
// Express.js webhook handler import crypto from "crypto"; function verifyWooCommerceWebhook(req: Request): boolean { const signature = req.headers["x-wc-webhook-signature"] as string; const payload = JSON.stringify(req.body); const expectedSignature = crypto .createHmac("sha256", process.env.WC_WEBHOOK_SECRET!) .update(payload) .digest("base64"); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); } // Route handler app.post("/webhooks/woocommerce", (req, res) => { if (!verifyWooCommerceWebhook(req)) { return res.status(401).json({ error: "Invalid signature" }); } const topic = req.headers["x-wc-webhook-topic"]; const payload = req.body; switch (topic) { case "order.created": handleNewOrder(payload); break; case "order.updated": handleOrderUpdate(payload); break; // ... other topics } res.status(200).json({ received: true }); });
# Python/Flask webhook handler import hmac import hashlib import base64 @app.route("/webhooks/woocommerce", methods=["POST"]) def woocommerce_webhook(): signature = request.headers.get("X-WC-Webhook-Signature") payload = request.get_data() expected = base64.b64encode( hmac.new( os.environ["WC_WEBHOOK_SECRET"].encode(), payload, hashlib.sha256 ).digest() ).decode() if not hmac.compare_digest(signature, expected): return {"error": "Invalid signature"}, 401 topic = request.headers.get("X-WC-Webhook-Topic") data = request.json if topic == "order.created": handle_new_order(data) elif topic == "order.updated": handle_order_update(data) return {"received": True}, 200
Categories & Tags
List Categories
async function getCategories() { const response = await api.get("products/categories", { per_page: 100, orderby: "name", }); return response.data; } // Create category async function createCategory(name: string, parentId?: number) { const response = await api.post("products/categories", { name, parent: parentId || 0, }); return response.data; }
List Tags
async function getTags() { const response = await api.get("products/tags", { per_page: 100, }); return response.data; }
Coupons
Create Coupon
async function createCoupon(data: CouponInput) { const response = await api.post("coupons", { code: data.code, discount_type: data.type, // percent, fixed_cart, fixed_product amount: data.amount.toString(), individual_use: true, exclude_sale_items: false, minimum_amount: data.minimumAmount?.toString(), maximum_amount: data.maximumAmount?.toString(), usage_limit: data.usageLimit, usage_limit_per_user: 1, date_expires: data.expiresAt, // ISO date string }); return response.data; }
Reports
Sales Report
async function getSalesReport(period = "month") { const response = await api.get("reports/sales", { period, // day, week, month, year }); return response.data; } // Top sellers async function getTopSellers(period = "month") { const response = await api.get("reports/top_sellers", { period, }); return response.data; }
Pagination
Handle Large Datasets
async function getAllProducts() { const allProducts = []; let page = 1; const perPage = 100; while (true) { const response = await api.get("products", { page, per_page: perPage, }); allProducts.push(...response.data); // Check headers for total pages const totalPages = parseInt(response.headers["x-wp-totalpages"]); if (page >= totalPages) break; page++; } return allProducts; }
Pagination Headers
| Header | Description |
|---|---|
| Total number of items |
| Total number of pages |
Error Handling
import WooCommerceRestApi from "@woocommerce/woocommerce-rest-api"; async function safeApiCall<T>( operation: () => Promise<{ data: T }> ): Promise<T> { try { const response = await operation(); return response.data; } catch (error: any) { if (error.response) { // API returned an error const { status, data } = error.response; switch (status) { case 400: throw new Error(`Bad request: ${data.message}`); case 401: throw new Error("Invalid API credentials"); case 404: throw new Error("Resource not found"); case 429: // Rate limited - wait and retry await new Promise(r => setTimeout(r, 5000)); return safeApiCall(operation); default: throw new Error(`API error: ${data.message}`); } } throw error; } } // Usage const products = await safeApiCall(() => api.get("products"));
Environment Variables
# .env WC_STORE_URL=https://your-store.com WC_CONSUMER_KEY=ck_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx WC_CONSUMER_SECRET=cs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx WC_WEBHOOK_SECRET=your_webhook_secret
Add to
credentials.md:
'WC_CONSUMER_KEY': r'ck_[a-f0-9]{40}', 'WC_CONSUMER_SECRET': r'cs_[a-f0-9]{40}',
Checklist
Before Integration
- WooCommerce plugin installed and activated
- HTTPS enabled on store
- Permalinks set to non-Plain setting
- API keys generated with appropriate permissions
- Webhook secret configured
Security
- API keys stored in environment variables
- Webhook signatures verified
- HTTPS used for all API calls
- Rate limiting handled
Testing
- Test API connection
- Test product CRUD operations
- Test order creation/updates
- Test webhook delivery
- Test pagination for large datasets
Anti-Patterns
- Plain permalinks - API won't work without pretty permalinks
- HTTP in production - Always use HTTPS
- Ignoring rate limits - WooCommerce may throttle requests
- Large single requests - Use pagination for bulk operations
- Storing keys in code - Use environment variables
- Skipping webhook verification - Always verify signatures