Commerce.js Integration
Overview
Commerce.js (Chec) is a headless commerce platform offering a JavaScript SDK that wraps its REST API for managing products, carts, checkouts, and orders. It is designed for developers who want to add commerce functionality to any website or JavaScript framework without the complexity of a full e-commerce platform. The SDK handles product fetching, cart lifecycle, checkout token creation, and order capture in a straightforward, promise-based API.
When to Use This Skill
- When you want to add e-commerce to a static site or simple React/Vue/Svelte project quickly
- When you don't need a complex backend and want a managed commerce API without server-side code
- When building a portfolio project, MVP, or proof-of-concept headless store
- When your catalog is small (under 1,000 products) and you don't need custom fulfillment logic
- When you want a simple SDK without managing a full Shopify or commercetools environment
Prerequisites & Platform Notes
This skill is written for custom/headless storefronts (Node.js, Python, or similar backend). The code examples use TypeScript/Node.js and can be adapted to any stack.
Shopify: Shopify Hydrogen is Shopify's headless framework. MACH/composable patterns apply when using Shopify as the commerce backend with a custom frontend, or when mixing Shopify with other best-of-breed services.
WooCommerce: WooCommerce can serve as a headless backend via its REST API and WPGraphQL. These patterns apply when decoupling the frontend from WordPress.
Magento: Magento's GraphQL API and PWA Studio support headless architectures. These composable patterns apply to Magento as a backend service in a MACH stack.
You'll need:
- Node.js 18+ (or adapt to your backend language)
- Stripe account and API keys
- An email sending service (SendGrid, AWS SES, or Postmark)
Core Instructions
Install the SDK and initialize the client
npm install @chec/commerce.js
import Commerce from '@chec/commerce.js';
// Public key is safe to expose in the browser
const commerce = new Commerce(process.env.NEXT_PUBLIC_CHEC_PUBLIC_KEY, true); // true = debug mode
Get your public API key from the Chec Dashboard under Developer → API keys.
Fetch and display products
// Fetch all products
const {data: products} = await commerce.products.list({
limit: 20,
page: 1,
sort_by: 'created',
sort_direction: 'desc',
});
// Fetch a single product by permalink (slug)
const product = await commerce.products.retrieve('my-product-slug', {type: 'permalink'});
// Products include structured data
console.log({
id: product.id,
name: product.name,
price: product.price.formatted_with_symbol, // "$29.99"
description: product.description, // HTML string from Chec CMS
image: product.image?.url,
variants: product.variants, // Size, color options
});
React component example:
import {useEffect, useState} from 'react';
import Commerce from '@chec/commerce.js';
const commerce = new Commerce(process.env.NEXT_PUBLIC_CHEC_PUBLIC_KEY!);
export function ProductGrid() {
const [products, setProducts] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
commerce.products.list()
.then(({data}) => setProducts(data))
.finally(() => setLoading(false));
}, []);
if (loading) return <div>Loading...</div>;
return (
<div className="grid grid-cols-3 gap-6">
{products.map(product => (
<div key={product.id} className="border rounded-lg p-4">
<img src={product.image?.url} alt={product.name} className="w-full h-48 object-cover" />
<h2 className="mt-2 font-semibold">{product.name}</h2>
<p className="text-gray-600">{product.price.formatted_with_symbol}</p>
<AddToCartButton productId={product.id} />
</div>
))}
</div>
);
}
Manage the cart
Commerce.js stores the cart ID in localStorage automatically:
// Get or create the current cart
const cart = await commerce.cart.retrieve();
// Add a product to the cart
const updatedCart = await commerce.cart.add(productId, quantity, {
// Optional: specify variant selections
variantId: 'variant_id_here',
optionId: 'option_id_here',
});
// Update line item quantity
await commerce.cart.update(lineItemId, {quantity: 3});
// Remove a line item
await commerce.cart.remove(lineItemId);
// Empty the cart
await commerce.cart.empty();
// Refresh the cart
const refreshedCart = await commerce.cart.refresh();
Cart state management with React Context:
// context/cart-context.tsx
import {createContext, useContext, useState, useEffect} from 'react';
import Commerce from '@chec/commerce.js';
const commerce = new Commerce(process.env.NEXT_PUBLIC_CHEC_PUBLIC_KEY!);
const CartContext = createContext<any>(null);
export function CartProvider({children}: {children: React.ReactNode}) {
const [cart, setCart] = useState<any>(null);
useEffect(() => { commerce.cart.retrieve().then(setCart); }, []);
const addToCart = async (productId: string, quantity = 1) => {
const {cart: updated} = await commerce.cart.add(productId, quantity);
setCart(updated);
};
const removeFromCart = async (lineItemId: string) => {
const {cart: updated} = await commerce.cart.remove(lineItemId);
setCart(updated);
};
return (
<CartContext.Provider value={{cart, addToCart, removeFromCart}}>
{children}
</CartContext.Provider>
);
}
export const useCart = () => useContext(CartContext);
Generate a checkout token and capture the order
// Step 1: Generate a checkout token from the cart
const checkoutToken = await commerce.checkout.generateToken(cart.id, {type: 'cart'});
// Step 2: Get live checkout information (shipping options, taxes)
const checkoutData = await commerce.checkout.getLive(checkoutToken.id);
// Step 3: Capture the order
const order = await commerce.checkout.capture(checkoutToken.id, {
customer: {
firstname: 'Jane',
lastname: 'Doe',
email: 'jane@example.com',
},
shipping: {
name: 'Jane Doe',
street: '123 Main Street',
town_city: 'San Francisco',
county_state: 'US-CA',
postal_zip_code: '94103',
country: 'US',
},
fulfillment: {
shipping_method: checkoutData.shipping.available_options[0].id,
},
payment: {
gateway: 'stripe',
stripe: {
payment_method_id: stripePaymentMethodId, // from Stripe.js
},
},
});
console.log(`Order #${order.customer_reference} placed!`);
// Clear the cart after successful order
await commerce.cart.refresh();
Handle product variants
const product = await commerce.products.retrieve(productId);
// Variants are organized as variant groups (e.g., "Size") with options (e.g., "S", "M", "L")
product.variants.forEach(variantGroup => {
console.log(`Variant group: ${variantGroup.name}`);
variantGroup.options.forEach(option => {
console.log(` - ${option.name}: +${option.price.formatted_with_symbol}`);
});
});
// When adding to cart, provide the full variant selection
const selections = {
[sizeVariantGroupId]: selectedSizeOptionId,
[colorVariantGroupId]: selectedColorOptionId,
};
await commerce.cart.add(product.id, 1, selections);
List and display orders
// Requires a customer JWT (obtained via commerce.customer.login)
const customerToken = await commerce.customer.login(email, password);
const {data: orders} = await commerce.orders.getAllForCustomer({
customer_token: customerToken.token,
});
orders.forEach(order => {
console.log({
reference: order.customer_reference,
status: order.status,
total: order.order_value.formatted_with_symbol,
items: order.order.line_items.map(item => item.product_name),
});
});
Examples
Complete Next.js product page
// app/products/[permalink]/page.tsx
import Commerce from '@chec/commerce.js';
import DOMPurify from 'isomorphic-dompurify';
// Server-side: use secret key to fetch product at build time
const commerce = new Commerce(process.env.CHEC_SECRET_KEY!);
export async function generateStaticParams() {
const {data: products} = await commerce.products.list({limit: 200});
return products.map((p: any) => ({permalink: p.permalink}));
}
export default async function ProductPage({params}: {params: {permalink: string}}) {
const product = await commerce.products.retrieve(params.permalink, {type: 'permalink'});
// Sanitize HTML from Chec CMS before rendering
const safeDescription = DOMPurify.sanitize(product.description ?? '');
return (
<main>
<img src={product.image?.url} alt={product.name} />
<h1>{product.name}</h1>
{/* Sanitized HTML rendered safely */}
<div dangerouslySetInnerHTML={{__html: safeDescription}} />
<p className="text-xl font-bold">{product.price.formatted_with_symbol}</p>
</main>
);
}
Cart item count badge
export function CartBadge() {
const {cart} = useCart();
const totalItems = cart?.total_unique_items ?? 0;
return (
<button className="relative">
<ShoppingCartIcon />
{totalItems > 0 && (
<span className="absolute -top-2 -right-2 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
{totalItems}
</span>
)}
</button>
);
}
Best Practices
- Use the public key on the client, secret key on the server — the public key can only read products and manage carts; the secret key can also manage orders and is not safe to expose in browser code
- Initialize the
Commerce client once — create a single instance in a module-level constant and import it; avoid creating a new instance on every render
- Handle webhook events for order automation — configure Chec webhooks in the dashboard to POST to your server when orders are placed, updated, or refunded
- Use checkout tokens, not cart IDs, for checkout —
generateToken creates a time-limited, checkout-specific token; always pass this token to capture, never the raw cart ID
- Refresh the cart after order capture — calling
commerce.cart.refresh() after a successful order creates a fresh empty cart; the old cart ID is invalidated
- Sanitize product description HTML before rendering — product descriptions are HTML from the Chec CMS; always run them through DOMPurify (
isomorphic-dompurify works in both Node.js and browser) before rendering
- Implement error handling for checkout capture — wrap
commerce.checkout.capture in try/catch and map commerce.error codes to user-friendly messages
Common Pitfalls
| Problem |
Solution |
Commerce is not a constructor |
Ensure you import as import Commerce from '@chec/commerce.js' (default import, not named) |
| Cart not persisting between page loads |
Commerce.js stores the cart in localStorage under chec_cart_id; ensure localStorage is available and not blocked |
| Checkout token expires |
Checkout tokens expire after 7 days; generate a new token from the cart immediately before starting checkout |
| Variant selections not applied |
Pass selections as the third argument to commerce.cart.add(): commerce.cart.add(id, qty, {variantGroupId: optionId}) |
| Payment gateway not configured |
Enable and configure the payment gateway (Stripe, Square, etc.) in the Chec Dashboard under Setup → Payment gateways |
Related Skills
- @jamstack-storefront
- @shopify-hydrogen
- @secure-checkout
- @webhook-architecture
1---2name: commerce-js-integration3description: Build a lightweight headless store using the Commerce.js SDK for product display, cart management, and checkout without a heavy backend4---56# Commerce.js Integration78## Overview910Commerce.js (Chec) is a headless commerce platform offering a JavaScript SDK that wraps its REST API for managing products, carts, checkouts, and orders. It is designed for developers who want to add commerce functionality to any website or JavaScript framework without the complexity of a full e-commerce platform. The SDK handles product fetching, cart lifecycle, checkout token creation, and order capture in a straightforward, promise-based API.1112## When to Use This Skill1314- When you want to add e-commerce to a static site or simple React/Vue/Svelte project quickly15- When you don't need a complex backend and want a managed commerce API without server-side code16- When building a portfolio project, MVP, or proof-of-concept headless store17- When your catalog is small (under 1,000 products) and you don't need custom fulfillment logic18- When you want a simple SDK without managing a full Shopify or commercetools environment1920## Prerequisites & Platform Notes2122**This skill is written for custom/headless storefronts** (Node.js, Python, or similar backend). The code examples use TypeScript/Node.js and can be adapted to any stack.2324**Shopify**: Shopify Hydrogen is Shopify's headless framework. MACH/composable patterns apply when using Shopify as the commerce backend with a custom frontend, or when mixing Shopify with other best-of-breed services.25**WooCommerce**: WooCommerce can serve as a headless backend via its REST API and WPGraphQL. These patterns apply when decoupling the frontend from WordPress.26**Magento**: Magento's GraphQL API and PWA Studio support headless architectures. These composable patterns apply to Magento as a backend service in a MACH stack.2728**You'll need**:29- Node.js 18+ (or adapt to your backend language)30- Stripe account and API keys31- An email sending service (SendGrid, AWS SES, or Postmark)3233## Core Instructions34351. **Install the SDK and initialize the client**3637 ```bash38 npm install @chec/commerce.js39 ```4041 ```javascript42 import Commerce from '@chec/commerce.js';4344 // Public key is safe to expose in the browser45 const commerce = new Commerce(process.env.NEXT_PUBLIC_CHEC_PUBLIC_KEY, true); // true = debug mode46 ```4748 Get your public API key from the Chec Dashboard under **Developer → API keys**.49502. **Fetch and display products**5152 ```javascript53 // Fetch all products54 const {data: products} = await commerce.products.list({55 limit: 20,56 page: 1,57 sort_by: 'created',58 sort_direction: 'desc',59 });6061 // Fetch a single product by permalink (slug)62 const product = await commerce.products.retrieve('my-product-slug', {type: 'permalink'});6364 // Products include structured data65 console.log({66 id: product.id,67 name: product.name,68 price: product.price.formatted_with_symbol, // "$29.99"69 description: product.description, // HTML string from Chec CMS70 image: product.image?.url,71 variants: product.variants, // Size, color options72 });73 ```7475 React component example:7677 ```tsx78 import {useEffect, useState} from 'react';79 import Commerce from '@chec/commerce.js';8081 const commerce = new Commerce(process.env.NEXT_PUBLIC_CHEC_PUBLIC_KEY!);8283 export function ProductGrid() {84 const [products, setProducts] = useState<any[]>([]);85 const [loading, setLoading] = useState(true);8687 useEffect(() => {88 commerce.products.list()89 .then(({data}) => setProducts(data))90 .finally(() => setLoading(false));91 }, []);9293 if (loading) return <div>Loading...</div>;9495 return (96 <div className="grid grid-cols-3 gap-6">97 {products.map(product => (98 <div key={product.id} className="border rounded-lg p-4">99 <img src={product.image?.url} alt={product.name} className="w-full h-48 object-cover" />100 <h2 className="mt-2 font-semibold">{product.name}</h2>101 <p className="text-gray-600">{product.price.formatted_with_symbol}</p>102 <AddToCartButton productId={product.id} />103 </div>104 ))}105 </div>106 );107 }108 ```1091103. **Manage the cart**111112 Commerce.js stores the cart ID in localStorage automatically:113114 ```javascript115 // Get or create the current cart116 const cart = await commerce.cart.retrieve();117118 // Add a product to the cart119 const updatedCart = await commerce.cart.add(productId, quantity, {120 // Optional: specify variant selections121 variantId: 'variant_id_here',122 optionId: 'option_id_here',123 });124125 // Update line item quantity126 await commerce.cart.update(lineItemId, {quantity: 3});127128 // Remove a line item129 await commerce.cart.remove(lineItemId);130131 // Empty the cart132 await commerce.cart.empty();133134 // Refresh the cart135 const refreshedCart = await commerce.cart.refresh();136 ```137138 Cart state management with React Context:139140 ```tsx141 // context/cart-context.tsx142 import {createContext, useContext, useState, useEffect} from 'react';143 import Commerce from '@chec/commerce.js';144145 const commerce = new Commerce(process.env.NEXT_PUBLIC_CHEC_PUBLIC_KEY!);146 const CartContext = createContext<any>(null);147148 export function CartProvider({children}: {children: React.ReactNode}) {149 const [cart, setCart] = useState<any>(null);150151 useEffect(() => { commerce.cart.retrieve().then(setCart); }, []);152153 const addToCart = async (productId: string, quantity = 1) => {154 const {cart: updated} = await commerce.cart.add(productId, quantity);155 setCart(updated);156 };157158 const removeFromCart = async (lineItemId: string) => {159 const {cart: updated} = await commerce.cart.remove(lineItemId);160 setCart(updated);161 };162163 return (164 <CartContext.Provider value={{cart, addToCart, removeFromCart}}>165 {children}166 </CartContext.Provider>167 );168 }169170 export const useCart = () => useContext(CartContext);171 ```1721734. **Generate a checkout token and capture the order**174175 ```javascript176 // Step 1: Generate a checkout token from the cart177 const checkoutToken = await commerce.checkout.generateToken(cart.id, {type: 'cart'});178179 // Step 2: Get live checkout information (shipping options, taxes)180 const checkoutData = await commerce.checkout.getLive(checkoutToken.id);181182 // Step 3: Capture the order183 const order = await commerce.checkout.capture(checkoutToken.id, {184 customer: {185 firstname: 'Jane',186 lastname: 'Doe',187 email: 'jane@example.com',188 },189 shipping: {190 name: 'Jane Doe',191 street: '123 Main Street',192 town_city: 'San Francisco',193 county_state: 'US-CA',194 postal_zip_code: '94103',195 country: 'US',196 },197 fulfillment: {198 shipping_method: checkoutData.shipping.available_options[0].id,199 },200 payment: {201 gateway: 'stripe',202 stripe: {203 payment_method_id: stripePaymentMethodId, // from Stripe.js204 },205 },206 });207208 console.log(`Order #${order.customer_reference} placed!`);209 // Clear the cart after successful order210 await commerce.cart.refresh();211 ```2122135. **Handle product variants**214215 ```javascript216 const product = await commerce.products.retrieve(productId);217218 // Variants are organized as variant groups (e.g., "Size") with options (e.g., "S", "M", "L")219 product.variants.forEach(variantGroup => {220 console.log(`Variant group: ${variantGroup.name}`);221 variantGroup.options.forEach(option => {222 console.log(` - ${option.name}: +${option.price.formatted_with_symbol}`);223 });224 });225226 // When adding to cart, provide the full variant selection227 const selections = {228 [sizeVariantGroupId]: selectedSizeOptionId,229 [colorVariantGroupId]: selectedColorOptionId,230 };231232 await commerce.cart.add(product.id, 1, selections);233 ```2342356. **List and display orders**236237 ```javascript238 // Requires a customer JWT (obtained via commerce.customer.login)239 const customerToken = await commerce.customer.login(email, password);240241 const {data: orders} = await commerce.orders.getAllForCustomer({242 customer_token: customerToken.token,243 });244245 orders.forEach(order => {246 console.log({247 reference: order.customer_reference,248 status: order.status,249 total: order.order_value.formatted_with_symbol,250 items: order.order.line_items.map(item => item.product_name),251 });252 });253 ```254255## Examples256257### Complete Next.js product page258259```tsx260// app/products/[permalink]/page.tsx261import Commerce from '@chec/commerce.js';262import DOMPurify from 'isomorphic-dompurify';263264// Server-side: use secret key to fetch product at build time265const commerce = new Commerce(process.env.CHEC_SECRET_KEY!);266267export async function generateStaticParams() {268 const {data: products} = await commerce.products.list({limit: 200});269 return products.map((p: any) => ({permalink: p.permalink}));270}271272export default async function ProductPage({params}: {params: {permalink: string}}) {273 const product = await commerce.products.retrieve(params.permalink, {type: 'permalink'});274 // Sanitize HTML from Chec CMS before rendering275 const safeDescription = DOMPurify.sanitize(product.description ?? '');276277 return (278 <main>279 <img src={product.image?.url} alt={product.name} />280 <h1>{product.name}</h1>281 {/* Sanitized HTML rendered safely */}282 <div dangerouslySetInnerHTML={{__html: safeDescription}} />283 <p className="text-xl font-bold">{product.price.formatted_with_symbol}</p>284 </main>285 );286}287```288289### Cart item count badge290291```tsx292export function CartBadge() {293 const {cart} = useCart();294 const totalItems = cart?.total_unique_items ?? 0;295296 return (297 <button className="relative">298 <ShoppingCartIcon />299 {totalItems > 0 && (300 <span className="absolute -top-2 -right-2 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">301 {totalItems}302 </span>303 )}304 </button>305 );306}307```308309## Best Practices310311- **Use the public key on the client, secret key on the server** — the public key can only read products and manage carts; the secret key can also manage orders and is not safe to expose in browser code312- **Initialize the `Commerce` client once** — create a single instance in a module-level constant and import it; avoid creating a new instance on every render313- **Handle webhook events for order automation** — configure Chec webhooks in the dashboard to POST to your server when orders are placed, updated, or refunded314- **Use checkout tokens, not cart IDs, for checkout** — `generateToken` creates a time-limited, checkout-specific token; always pass this token to `capture`, never the raw cart ID315- **Refresh the cart after order capture** — calling `commerce.cart.refresh()` after a successful order creates a fresh empty cart; the old cart ID is invalidated316- **Sanitize product description HTML before rendering** — product descriptions are HTML from the Chec CMS; always run them through DOMPurify (`isomorphic-dompurify` works in both Node.js and browser) before rendering317- **Implement error handling for checkout capture** — wrap `commerce.checkout.capture` in try/catch and map `commerce.error` codes to user-friendly messages318319## Common Pitfalls320321| Problem | Solution |322|---------|----------|323| `Commerce is not a constructor` | Ensure you import as `import Commerce from '@chec/commerce.js'` (default import, not named) |324| Cart not persisting between page loads | Commerce.js stores the cart in localStorage under `chec_cart_id`; ensure localStorage is available and not blocked |325| Checkout token expires | Checkout tokens expire after 7 days; generate a new token from the cart immediately before starting checkout |326| Variant selections not applied | Pass selections as the third argument to `commerce.cart.add()`: `commerce.cart.add(id, qty, {variantGroupId: optionId})` |327| Payment gateway not configured | Enable and configure the payment gateway (Stripe, Square, etc.) in the Chec Dashboard under **Setup → Payment gateways** |328329## Related Skills330331- @jamstack-storefront332- @shopify-hydrogen333- @secure-checkout334- @webhook-architecture