
Commerce Js Integration
- 58 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Build a lightweight headless store with the Commerce.js (Chec) SDK for product display, cart management, and checkout without a heavy backend.
About
A skill for using the Commerce.js SDK to add headless commerce (products, carts, checkouts, orders) to any JavaScript site. A developer uses it to add commerce to a frontend without a full ecommerce platform.
- Promise-based SDK wrapping Chec's REST API
- Handles product fetch, cart lifecycle, checkout tokens, orders
Commerce Js Integration by the numbers
- 58 all-time installs (skills.sh)
- Ranked #3,178 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill commerce-js-integrationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 58 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Build a lightweight headless store with the Commerce.js (Chec) SDK for product display, cart management, and checkout without a heavy backend.
Files
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
1. 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 modeGet your public API key from the Chec Dashboard under Developer → API keys.
2. 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>
);
}3. 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);4. 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();5. 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);6. 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 —
generateTokencreates a time-limited, checkout-specific token; always pass this token tocapture, 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-dompurifyworks in both Node.js and browser) before rendering - Implement error handling for checkout capture — wrap
commerce.checkout.capturein try/catch and mapcommerce.errorcodes 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
{
"context": "Tests whether the agent correctly manages cart state using Commerce.js APIs, uses a single module-level client instance, passes variant selections in the correct format, uses the right cart API methods, and accesses the correct cart property for item count.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Single Commerce instance",
"max_score": 10,
"description": "The Commerce client is created exactly once, at module level (not inside useEffect, component functions, or event handlers)"
},
{
"name": "cart.retrieve() on mount",
"max_score": 8,
"description": "Calls `commerce.cart.retrieve()` to get or create the cart (e.g. in useEffect or on initialization)"
},
{
"name": "cart.add() with variant selections",
"max_score": 12,
"description": "Calls `commerce.cart.add(productId, quantity, selections)` where `selections` is an object mapping variant group IDs to option IDs (third argument)"
},
{
"name": "cart.update() for quantity",
"max_score": 8,
"description": "Calls `commerce.cart.update(lineItemId, {quantity: N})` to update a line item's quantity"
},
{
"name": "cart.remove() for line items",
"max_score": 8,
"description": "Calls `commerce.cart.remove(lineItemId)` to remove a specific item from the cart"
},
{
"name": "cart.empty() to clear cart",
"max_score": 8,
"description": "Calls `commerce.cart.empty()` to empty/clear the entire cart"
},
{
"name": "React Context pattern",
"max_score": 8,
"description": "Uses React's createContext and a Provider component to share cart state across the component tree"
},
{
"name": "useCart hook exported",
"max_score": 6,
"description": "Exports a `useCart` hook (or equivalent) that returns cart state and cart action functions"
},
{
"name": "total_unique_items for badge",
"max_score": 12,
"description": "Uses `cart.total_unique_items` (not `cart.total_items` or a manual count) for the cart item count display"
},
{
"name": "Variant group ID as key",
"max_score": 10,
"description": "When building the variant selections object, uses the variant group ID as the key and option ID as the value (not option name or group name)"
},
{
"name": "Public key for client",
"max_score": 10,
"description": "The Commerce client is initialized with the public API key (an environment variable), not the secret key"
}
]
}
Shopping Cart System for a Clothing Boutique
Problem/Feature Description
"Thread & Co" is a small clothing boutique that sells items with multiple variant options — each piece of clothing comes in different sizes and colors. They've chosen Commerce.js as their headless commerce backend and now need a complete cart implementation for their React storefront. The app is a single-page application and the cart state needs to be shared across the entire component tree, from the navigation header (which shows a cart item count) down to individual product detail pages.
The tech lead has emphasized that the cart logic should be centralized and reusable. There have been past issues in the app where multiple instances of the SDK client caused inconsistent state, so the client initialization must be handled carefully. Products on the site all have size and color options, and users need to be able to select their preferred variant before adding to cart.
Output Specification
Write the following files:
1. context/CartContext.tsx (or .jsx) — A React context that:
- Retrieves the cart on mount
- Provides functions:
addToCart(productId, quantity, variantSelections),updateItem(lineItemId, quantity),removeItem(lineItemId),clearCart() - Exports both the provider component and a
useCarthook
2. components/AddToCartButton.tsx (or .jsx) — A component that:
- Accepts
productId,variants(an array of variant groups each with options), and optionalquantityprop - Renders size and color selectors based on the provided variants
- Calls
addToCartwith the appropriate selections on submit
3. components/CartIcon.tsx (or .jsx) — A navigation component that shows the number of items in the cart using an appropriate cart property.
You do not need a working Chec account — the code should be structured correctly and reference appropriate environment variables. Do not make live API calls.
Input Files
The following sample product data is provided to help understand the variant structure. Extract it before beginning.
=============== FILE: inputs/sample-product.json =============== { "id": "prod_NqKE50BR4wdgBL", "name": "Classic Wool Coat", "price": { "raw": 189.00, "formatted": "189.00", "formatted_with_symbol": "$189.00" }, "variants": [ { "id": "vgrp_bO6J5aB4aMNNwl", "name": "Size", "options": [ { "id": "optn_bPj2XLmEd6ANQB", "name": "S", "price": { "raw": 0, "formatted_with_symbol": "$0.00" } }, { "id": "optn_Kvg9l6oHEOrQO3", "name": "M", "price": { "raw": 0, "formatted_with_symbol": "$0.00" } }, { "id": "optn_mOd3mb8q5b3xKO", "name": "L", "price": { "raw": 10, "formatted_with_symbol": "+$10.00" } } ] }, { "id": "vgrp_4WJvlKpg7pwbYV", "name": "Color", "options": [ { "id": "optn_7eNo0Wr9kpn8Q6", "name": "Camel", "price": { "raw": 0, "formatted_with_symbol": "$0.00" } }, { "id": "optn_ZrJ2Kl0X1kw9P3", "name": "Charcoal", "price": { "raw": 0, "formatted_with_symbol": "$0.00" } } ] } ] }
{
"context": "Tests whether the agent correctly implements the Commerce.js checkout flow: generating tokens (not using cart IDs directly), calling getLive for shipping options, capturing orders with required fields, refreshing the cart post-purchase, handling errors, and using the secret key server-side.",
"type": "weighted_checklist",
"checklist": [
{
"name": "generateToken with cart type",
"max_score": 12,
"description": "Calls `commerce.checkout.generateToken(cartId, {type: 'cart'})` — uses the cart ID with the `{type: 'cart'}` parameter to generate the checkout token"
},
{
"name": "getLive for shipping options",
"max_score": 10,
"description": "Calls `commerce.checkout.getLive(checkoutTokenId)` to retrieve live checkout data including available shipping options"
},
{
"name": "capture uses token not cart ID",
"max_score": 10,
"description": "Passes the checkout token ID (not the cart ID) to `commerce.checkout.capture()`"
},
{
"name": "Shipping method from getLive options",
"max_score": 8,
"description": "Uses a shipping method ID sourced from `checkoutData.shipping.available_options` when constructing the capture payload"
},
{
"name": "Capture payload structure",
"max_score": 8,
"description": "The capture call includes all required top-level fields: `customer`, `shipping`, `fulfillment`, and `payment`"
},
{
"name": "Stripe payment gateway",
"max_score": 8,
"description": "Specifies `gateway: 'stripe'` in the payment object and includes `stripe.payment_method_id` from the Stripe.js token"
},
{
"name": "cart.refresh() after order",
"max_score": 10,
"description": "Calls `commerce.cart.refresh()` after a successful order capture to reset the cart to a fresh empty state"
},
{
"name": "try/catch error handling",
"max_score": 8,
"description": "Wraps `commerce.checkout.capture()` in a try/catch block and handles errors (does not let unhandled promise rejections propagate)"
},
{
"name": "Secret key server-side only",
"max_score": 10,
"description": "The Next.js API route (orders.ts) uses the Chec secret key (e.g. `CHEC_SECRET_KEY`) while client-side code uses the public key"
},
{
"name": "customer.login for orders",
"max_score": 8,
"description": "The orders API route calls `commerce.customer.login(email, password)` to get a customer token before calling `commerce.orders.getAllForCustomer()`"
},
{
"name": "customer_token passed to orders",
"max_score": 8,
"description": "Passes `{customer_token: token}` to `commerce.orders.getAllForCustomer()` (not a raw session ID or header)"
}
]
}
Checkout Flow Implementation
Problem/Feature Description
"Shelf & Spine" is an independent bookshop that built its storefront with Commerce.js and has a working product catalog and cart. The final piece they need is a complete checkout flow. Customers fill out a shipping form, see available shipping methods retrieved from the platform, and pay via Stripe. The team has already integrated Stripe.js on the frontend and can provide a paymentMethodId from it — they just need the Commerce.js checkout logic wired up.
The engineering team had a bad experience with an earlier checkout implementation that left stale cart state after purchases, causing customers to see old cart contents after placing an order. They also want proper error handling so that if an order fails to capture, users see a meaningful message rather than a generic crash. A Next.js API route will handle the server-side parts that need elevated API access, while the client-side form handles the user interactions.
Output Specification
Write the following files:
1. lib/checkout.ts (or .js) — A module with functions:
generateCheckoutToken(cartId: string)— creates a checkout token from a cartgetShippingOptions(checkoutTokenId: string)— retrieves available shipping options for the checkoutcaptureOrder(checkoutTokenId: string, orderData: object)— captures the order; must include error handling
2. pages/api/orders.ts (or .js) — A Next.js API route that:
- Lists orders for an authenticated customer (requires customer login first)
- Uses the appropriate key type for server-side operations
3. components/CheckoutForm.tsx (or .jsx) — A React component that:
- Accepts
cartIdandstripePaymentMethodIdas props - Walks through the checkout steps: generate token → get live data → capture order
- Clears the cart state after a successful order
- Shows an error message if the order capture fails
Write a checkout-flow.md document explaining the checkout steps and what happens to the cart after a successful order.
You do not need a working Chec account or Stripe account — structure the code correctly and use appropriate environment variables. Do not make live API calls.
{
"context": "Tests whether the agent correctly initializes the Commerce.js client (package, import style, single instance, correct key usage), uses the right API methods for fetching products, handles product data fields correctly, and sanitizes HTML descriptions before rendering.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Correct package name",
"max_score": 8,
"description": "Uses `@chec/commerce.js` as the package name (not `commercejs`, `commerce.js`, or any other variant)"
},
{
"name": "Default import style",
"max_score": 10,
"description": "Imports Commerce as a default import: `import Commerce from '@chec/commerce.js'` (not a named import like `import { Commerce }`)"
},
{
"name": "Module-level client instance",
"max_score": 10,
"description": "The Commerce client is created once at module level (not inside a function, hook, or component body)"
},
{
"name": "Public key env var",
"max_score": 8,
"description": "Uses an environment variable for the public API key (e.g. `NEXT_PUBLIC_CHEC_PUBLIC_KEY` or similar) rather than a hardcoded string"
},
{
"name": "products.list() method",
"max_score": 8,
"description": "Calls `commerce.products.list()` to fetch all products"
},
{
"name": "products.retrieve() with permalink type",
"max_score": 10,
"description": "Calls `commerce.products.retrieve(permalink, {type: 'permalink'})` to fetch a single product by its slug/permalink"
},
{
"name": "Price formatted with symbol",
"max_score": 10,
"description": "Uses `product.price.formatted_with_symbol` to display price (not `product.price.raw` or `product.price.formatted`)"
},
{
"name": "Optional image access",
"max_score": 8,
"description": "Accesses the product image URL with optional chaining (`product.image?.url`) or includes a null check before accessing"
},
{
"name": "DOMPurify for HTML sanitization",
"max_score": 14,
"description": "Uses DOMPurify (specifically `isomorphic-dompurify`) to sanitize product description HTML before rendering it"
},
{
"name": "dangerouslySetInnerHTML after sanitization",
"max_score": 8,
"description": "Renders the sanitized description with `dangerouslySetInnerHTML` (not as raw text, and NOT without sanitizing first)"
},
{
"name": "Public key not secret key",
"max_score": 6,
"description": "Client-side code uses the public key (NOT the secret key); no usage of `CHEC_SECRET_KEY` in browser/component code"
}
]
}
Headless Store Product Catalog Page
Problem/Feature Description
A boutique lifestyle brand called "Nomad Goods" is launching a new website built with Next.js. They already have their product catalog set up in the Chec platform and now need a developer to wire up the frontend to display their products. The marketing team writes rich product descriptions in the Chec dashboard CMS, including formatting like bold text, bullet lists, and links — so the storefront needs to render this content properly.
The engineering lead wants the product page code to follow best practices: the Commerce.js client should be initialized cleanly and reused across the app, prices should be displayed appropriately, and product images should be handled gracefully even when a product has no image assigned.
Output Specification
Write a self-contained Next.js-style module at store/products.ts (or .js) that:
- Exports a pre-initialized Commerce.js client as a named export
commerce - Exports an async function
fetchProducts()that returns a list of products - Exports an async function
fetchProduct(permalink: string)that returns a single product by permalink
Also write a React component at components/ProductCard.tsx (or .jsx) that:
- Accepts a single
productprop - Displays the product name, price (with currency symbol), image (if present), and description
- Renders the description content safely
Write a README.md explaining how the public API key should be configured and what environment variable names are expected.
You do not need to have a working Chec account — the code should be structured correctly and reference appropriate environment variables. Do not attempt to make live API calls.
{
"name": "finsi/commerce-js-integration",
"version": "0.1.0",
"summary": "Commerce.js (Chec) SDK integration for lightweight headless stores",
"skills": {
"commerce-js-integration": {
"path": "SKILL.md"
}
}
}