Products, Variants & Categories
This guide explains how to model product variations and organize your catalog using BareCommerce's flexible product system.
Overview: The Flat Variant Model
BareCommerce uses a flat variant model instead of a traditional hierarchical structure:
- No separate
Varianttable — Variants are just products with shared metadata - Grouped by
variantGroupId— All variants of the same product share a group ID - Independent products — Each variant is a complete product record with its own price, SKU, stock, and attributes
- Flexible attributes — Store custom attribute data (color, size, material, etc.) as JSON
This approach is powerful because:
- Each variant can have different pricing and inventory
- Variants can be managed independently via API
- You can publish/unpublish individual variants
- Simple queries return all information you need
Understanding Variants
What is a Variant?
A variant is a specific version of a product. For example, a "T-Shirt" product might have variants:
Product Title: "Classic T-Shirt"
├─ Variant 1: Red / Size M — SKU: TSHIRT-RED-M, Price: $19.99, Stock: 45
├─ Variant 2: Red / Size L — SKU: TSHIRT-RED-L, Price: $19.99, Stock: 32
├─ Variant 3: Blue / Size M — SKU: TSHIRT-BLUE-M, Price: $19.99, Stock: 0
└─ Variant 4: Blue / Size L — SKU: TSHIRT-BLUE-L, Price: $19.99, Stock: 28In BareCommerce, each of these would be a separate Product record grouped under the same variantGroupId.
Variant Structure
Each variant has these key fields:
{
// Shared variant group identity
variantGroupId: "group_abc123", // Ties all variants together
// Variant-specific attributes
attributes: {
"color": "red", // JSON object of custom attributes
"size": "M",
"material": "cotton"
},
// Variant-specific properties
sku: "TSHIRT-RED-M", // Unique per variant
barcode: "123456789", // Optional
// Variant-specific pricing
price: "19.99", // String for precision
compareAtPrice: "24.99", // Original/sale price
// Variant-specific inventory
trackStock: true,
stock: 45,
allowBackorder: false,
// Common product info (same for all variants)
title: "Classic T-Shirt",
description: "Comfortable everyday t-shirt",
slug: "classic-t-shirt",
categoryIds: ["cat_1", "cat_2"], // All variants share categories
}Creating Your First Variant Group
To create a product with variants, follow this workflow:
Step 1: Generate a Variant Group ID
const variantGroupId = `vg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Example: "vg_1699564800000_a1b2c3d4e"Step 2: Create Each Variant
Create multiple product records with the same variantGroupId:
# Create Variant 1: Red / Size M
curl https://api.barecommercecore.com/api/stores/{storeId}/products \
-H "X-API-Key: sk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"title": "Classic T-Shirt",
"slug": "classic-t-shirt",
"description": "Comfortable everyday t-shirt",
"price": "19.99",
"compareAtPrice": "24.99",
"sku": "TSHIRT-RED-M",
"barcode": "123456789",
"variantGroupId": "vg_1699564800000_a1b2c3d4e",
"attributes": {
"color": "red",
"size": "M"
},
"trackStock": true,
"stock": 45,
"allowBackorder": false,
"categoryIds": ["cat_shirts", "cat_red"],
"primaryMediaId": "media_1"
}'
# Create Variant 2: Red / Size L
curl https://api.barecommercecore.com/api/stores/{storeId}/products \
-H "X-API-Key: sk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"title": "Classic T-Shirt",
"slug": "classic-t-shirt-l",
"description": "Comfortable everyday t-shirt",
"price": "19.99",
"compareAtPrice": "24.99",
"sku": "TSHIRT-RED-L",
"barcode": "987654321",
"variantGroupId": "vg_1699564800000_a1b2c3d4e",
"attributes": {
"color": "red",
"size": "L"
},
"trackStock": true,
"stock": 32,
"allowBackorder": false,
"categoryIds": ["cat_shirts", "cat_red"],
"primaryMediaId": "media_1"
}'Note: The slug must be unique per store. If all variants share the same slug, BareCommerce will reject the second variant. Use a strategy like classic-t-shirt, classic-t-shirt-l, classic-t-shirt-xl to avoid collisions.
Step 3: Query All Variants Together
Fetch all variants of a product group:
curl "https://api.barecommercecore.com/api/stores/{storeId}/products/by-variant-group?variantGroupId=vg_1699564800000_a1b2c3d4e" \
-H "X-API-Key: sk_live_xxxxx"Response:
{
"items": [
{
"id": "prod_1",
"title": "Classic T-Shirt",
"sku": "TSHIRT-RED-M",
"attributes": { "color": "red", "size": "M" },
"price": "19.99",
"stock": 45,
"variantGroupId": "vg_1699564800000_a1b2c3d4e"
},
{
"id": "prod_2",
"title": "Classic T-Shirt",
"sku": "TSHIRT-RED-L",
"attributes": { "color": "red", "size": "L" },
"price": "19.99",
"stock": 32,
"variantGroupId": "vg_1699564800000_a1b2c3d4e"
}
],
"total": 2
}Variant Attributes: Best Practices
Attributes are stored as a JSON object. Here's how to structure them:
// Good: Clear, machine-readable keys
attributes: {
"color": "red",
"size": "M",
"material": "cotton",
"fit": "regular"
}
// Bad: Inconsistent or unclear
attributes: {
"Color": "red", // Don't mix casing
"sz": "M", // Use full names
"Material_Type": "cotton" // Don't use underscores in keys
}Recommended attribute naming:
- Use lowercase, snake_case keys
- Keep values simple strings
- Common attributes:
color,size,material,weight,flavor,pattern
Variant Inventory Management
Each variant tracks its own inventory independently:
// Variant 1: In stock
{
sku: "SHIRT-RED-M",
trackStock: true,
stock: 45,
allowBackorder: false
}
// Variant 2: Low stock
{
sku: "SHIRT-BLUE-M",
trackStock: true,
stock: 2,
allowBackorder: false
}
// Variant 3: Out of stock but backordered
{
sku: "SHIRT-GREEN-M",
trackStock: true,
stock: 0,
allowBackorder: true
}
// Variant 4: Not tracked (infinite stock)
{
sku: "DIGITAL-DOWNLOAD",
trackStock: false,
stock: 0, // Ignored when trackStock=false
allowBackorder: false
}When a customer purchases a variant, you (or your inventory system) must decrement the stock field:
# Update stock after purchase
curl "https://api.barecommercecore.com/api/stores/{storeId}/products/{variantId}" \
-X PATCH \
-H "X-API-Key: sk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"stock": 44 // Was 45, now 44 after 1 sale
}'Displaying Variants to Customers
When building a storefront, fetch all variants and group them by attribute:
// Fetch all variants
const response = await fetch(
`https://api.barecommercecore.com/api/stores/${storeId}/products/by-variant-group?variantGroupId=${variantGroupId}`,
{ headers }
);
const { items } = await response.json();
// Group by color
const byColor = {};
items.forEach(variant => {
const color = variant.attributes.color;
if (!byColor[color]) byColor[color] = [];
byColor[color].push(variant);
});
// Render
Object.entries(byColor).forEach(([color, variants]) => {
console.log(`${color}: ${variants.map(v => v.attributes.size).join(', ')}`);
});
// Output:
// Red: M, L, XL
// Blue: M, L
// Green: MUnderstanding Categories
What is a Category?
Categories organize products into a taxonomy. Examples:
Store
├─ Clothing
│ ├─ T-Shirts
│ ├─ Jeans
│ └─ Jackets
├─ Footwear
│ ├─ Sneakers
│ ├─ Boots
│ └─ Sandals
└─ Accessories
├─ Hats
├─ Belts
└─ ScarvesCreating Categories
Categories support hierarchical structure with up to 2 levels (parent + children):
# Create parent category
curl https://api.barecommercecore.com/api/stores/{storeId}/categories \
-X POST \
-H "X-API-Key: sk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Clothing",
"slug": "clothing",
"status": "published",
"description": "All clothing items"
}'
# Response
{
"id": "cat_clothing",
"name": "Clothing",
"slug": "clothing",
"status": "published",
"parentId": null,
"createdAt": "2024-01-15T10:30:00Z"
}# Create child category
curl https://api.barecommercecore.com/api/stores/{storeId}/categories \
-X POST \
-H "X-API-Key: sk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "T-Shirts",
"slug": "t-shirts",
"status": "published",
"parentId": "cat_clothing",
"description": "Casual and formal t-shirts"
}'
# Response
{
"id": "cat_tshirts",
"name": "T-Shirts",
"slug": "t-shirts",
"status": "published",
"parentId": "cat_clothing"
}Category Limits
- Maximum depth: 2 levels — One parent, one child level
- No circular references — A category cannot be its own parent
- Unique slugs — Each category slug must be unique per store
Assigning Products to Categories
Products belong to zero or more categories via the categoryIds array:
# Create product in multiple categories
curl https://api.barecommercecore.com/api/stores/{storeId}/products \
-X POST \
-H "X-API-Key: sk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"title": "Classic T-Shirt",
"slug": "classic-t-shirt",
"price": "19.99",
"categoryIds": ["cat_clothing", "cat_tshirts", "cat_sales"],
"sku": "TSHIRT-001"
}'A product can be in multiple categories simultaneously. For example:
- A red shirt could be in
Clothing→T-Shirtsand also inSale Items
Querying Products by Category
BareCommerce doesn't have a direct "get products in category" endpoint, but you can:
- Get all products and filter client-side:
const response = await fetch(
`https://api.barecommercecore.com/api/stores/${storeId}/products`,
{ headers }
);
const { items } = await response.json();
// Filter to products in "cat_tshirts"
const categoryProducts = items.filter(product =>
product.categoryIds.includes("cat_tshirts")
);- Store custom indexes — Build your own mapping via webhooks or periodic sync
Category Hierarchy
Fetch the full category tree:
# Get all categories (flat list)
curl "https://api.barecommercecore.com/api/stores/{storeId}/categories" \
-H "X-API-Key: sk_live_xxxxx"
# Get only root categories (no parent)
curl "https://api.barecommercecore.com/api/stores/{storeId}/categories?parentId=null" \
-H "X-API-Key: sk_live_xxxxx"
# Get children of a specific category
curl "https://api.barecommercecore.com/api/stores/{storeId}/categories?parentId=cat_clothing" \
-H "X-API-Key: sk_live_xxxxx"Build a tree in your application:
const buildCategoryTree = (categories) => {
const byId = Object.fromEntries(categories.map(c => [c.id, { ...c, children: [] }]));
const root = [];
categories.forEach(category => {
if (category.parentId) {
byId[category.parentId].children.push(byId[category.id]);
} else {
root.push(byId[category.id]);
}
});
return root;
};Common Patterns
Pattern 1: Clothing Store with Size & Color Variants
// Create size/color attributes
const colors = ["Black", "White", "Navy"];
const sizes = ["XS", "S", "M", "L", "XL", "XXL"];
// Generate variant group
const variantGroupId = `shirt_${Date.now()}`;
// Create variant for each combination
for (const color of colors) {
for (const size of sizes) {
const sku = `SHIRT-${color.toUpperCase()}-${size}`;
const stock = Math.floor(Math.random() * 100); // Random for demo
await createProduct({
variantGroupId,
attributes: { color, size },
sku,
stock,
// ... other fields
});
}
}Pattern 2: Digital Products (Infinite Stock)
await createProduct({
title: "PDF Guide",
description: "Instant download guide",
price: "9.99",
sku: "PDF-GUIDE-001",
trackStock: false, // No inventory tracking
stock: 0, // Ignored
variantGroupId: null, // Single product, no variants
});Pattern 3: Seasonal Sales Category
# Create a "Summer Sale" category
curl https://api.barecommercecore.com/api/stores/{storeId}/categories \
-X POST \
-H "X-API-Key: sk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer Sale",
"slug": "summer-sale",
"status": "published"
}'
# Assign multiple products to it
# (Update each product with categoryIds including the sale category)Pattern 4: Product Collections (Multiple Categories)
// One product in multiple independent categories
const productIds = ["prod_1", "prod_2", "prod_3"];
productIds.forEach(productId => {
updateProduct(productId, {
categoryIds: [
"cat_featured", // Featured products
"cat_bestsellers", // Best sellers
"cat_summer_2024" // Seasonal
]
});
});API Reference
Variant Queries
# List products with specific SKU
GET /api/stores/{storeId}/products?sku=SHIRT-RED-M
# List all variants in a group
GET /api/stores/{storeId}/products/by-variant-group?variantGroupId=vg_xxx
# Filter by attribute (custom implementation)
GET /api/stores/{storeId}/products/by-attributes?color=red&size=MCategory Queries
# List all categories
GET /api/stores/{storeId}/categories
# Get root categories only
GET /api/stores/{storeId}/categories?parentId=null
# Get children of a category
GET /api/stores/{storeId}/categories?parentId=cat_clothing
# Search categories
GET /api/stores/{storeId}/categories?search=shirtBest Practices
Variant Naming
- Use a consistent SKU format:
PRODUCT-VARIANT-ID - Make SKUs human-readable (e.g.,
SHIRT-RED-MnotSKU12345) - Avoid special characters in SKUs
Category Organization
- Limit to 2 levels for simplicity
- Use singular nouns in category names ("T-Shirt", not "T-Shirts")
- Keep slug names lowercase and dash-separated
- Don't create categories you won't use
Inventory Accuracy
- Only set
trackStock: trueif you actually manage inventory - Update stock immediately after orders (via webhook listener)
- Use
allowBackorder: trueonly for items you can restock - Sync with your warehouse system regularly
SEO Optimization
- Fill
seoTitleandseoDescriptionfor all variants - Use category
seoTitlefor better search rankings - Include relevant keywords in descriptions
Troubleshooting
Variant Not Appearing
Problem: Created a variant but it's not showing up in listings.
Solution:
- Check the
variantGroupId— make sure all variants have the same ID - Verify the product
statusis"published", not"draft" - Confirm you're querying the correct
storeId
Slug Uniqueness Error
Problem: Getting error Unique constraint failed: products.storeId, products.slug
Solution:
- Variant slugs must be unique across all variants
- Use suffixes like
product-name,product-name-l,product-name-xl - Or include the full variant combo:
shirt-red-medium
Category Not Showing Products
Problem: Created a category and assigned products, but products don't appear.
Solution:
- Categories are independent — filtering happens via
categoryIdsarray on products - Build your own "products in category" view using the product
categoryIdsfield - Ensure product
statusis"published"
Next Steps
- Products API Reference — Detailed endpoint docs
- Best Practices — General platform guidance
- Getting Started — Full platform overview