Platform
The @commercejs/platform package is the built-in CommerceJS commerce engine. It provides a fully functional eCommerce backend powered by Neon Postgres — zero external APIs, zero configuration. It includes both a storefront adapter and a full Admin API for merchant operations.
Installation
pnpm add @commercejs/platform @commercejs/types
Quick Start
import { createPlatformAdapter, seedDrizzle } from '@commercejs/platform'
// Create the adapter (auto-connects to Neon via DATABASE_URL)
const { adapter, admin } = await createPlatformAdapter({ currency: 'SAR' })
// Use the storefront adapter — same interface as any CommerceAdapter
const products = await adapter.getProducts({ limit: 10 })
const cart = await adapter.createCart()
// Use the admin API for merchant operations
const stats = await admin.getDashboardStats()
With the orchestration engine
import { createCommerce } from '@commercejs/core'
import { createPlatformAdapter } from '@commercejs/platform'
const { adapter } = await createPlatformAdapter({ currency: 'SAR' })
const commerce = createCommerce({ adapter })
const products = await commerce.getProducts({ query: 'shirt' })
Configuration
The createPlatformAdapter() function is async and returns a PlatformAdapterResult:
interface PlatformAdapterResult {
/** Storefront adapter implementing CommerceAdapter */
adapter: CommerceAdapter
/** Admin API for merchant operations */
admin: AdminAPI
}
PlatformConfig
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
currency | string | No | 'SAR' | Store currency code for price formatting |
locale | string | No | 'en' | Default locale |
connectionString | string | No | DATABASE_URL env | Neon Postgres connection string |
DATABASE_URL is required. Either pass connectionString in config or set the DATABASE_URL environment variable. The database connection is automatically initialized when createPlatformAdapter() is called.Database
The platform uses Neon Postgres via Drizzle ORM. The database schema is automatically created and migrated on startup.
# Set your Neon connection string
DATABASE_URL="postgres://user:pass@ep-xxx.neon.tech/commerce"
The Drizzle schema is exported for advanced use cases:
import { schema } from '@commercejs/platform'
// Access any table definition: schema.products, schema.orders, etc.
Implemented Domains
The platform adapter implements 12 commerce domains:
Core domains
| Domain | Methods |
|---|---|
| Catalog | getProduct, getProducts, getCategories |
| Cart | createCart, getCart, addToCart, updateCartItem, removeFromCart, applyCoupon, removeCoupon |
| Checkout | getShippingMethods, setShippingAddress, setBillingAddress, setShippingMethod, getPaymentMethods, setPaymentMethod, placeOrder |
| Orders | createOrder, getOrder, getCustomerOrders, getOrderStatuses, updateOrderStatus, cancelOrder, duplicateOrder, getOrderHistory |
| Customers | login, register, getCustomer, updateCustomer, logout, forgotPassword, resetPassword, getAddresses, addAddress, updateAddress, deleteAddress |
| Store | getStoreInfo |
Extended domains
| Domain | Methods |
|---|---|
| Brands | getBrands |
| Countries | getCountries |
| Wishlist | getWishlist, addToWishlist, removeFromWishlist |
| Reviews | getProductReviews, getReviewSummary, submitReview |
| Promotions | getActivePromotions, validateCoupon |
| Returns | createReturn, getReturn, getReturns, getOrderReturns, cancelReturn |
Not yet implemented
These domains throw NOT_SUPPORTED errors (HTTP 501):
wholesale · auctions · rentals · gift-cards · locations
Admin API
The admin object returned by createPlatformAdapter() provides merchant-facing operations for building dashboards, admin panels, and management UIs.
Authentication
Admin users are stored in the database with hashed passwords and role-based access:
// Login
const admin = await admin.auth.login('admin@store.com', 'password')
// Create a new admin
await admin.auth.createAdmin({
email: 'editor@store.com',
password: 'securepass',
name: 'Editor',
role: 'editor', // 'owner' | 'admin' | 'editor'
})
// List all admins
const admins = await admin.auth.listAdmins()
Initial admin seeding: on first startup, seedInitialAdmin() is called automatically to create an admin user from ADMIN_EMAIL and ADMIN_PASSWORD environment variables.
Products
// Create a product
const product = await admin.createProduct({
name: 'Premium T-Shirt',
nameAr: 'قميص فاخر',
price: 49.99,
currency: 'SAR',
status: 'active',
categories: ['cat-clothing'],
images: [{ url: 'https://...', isPrimary: true }],
variants: [{ sku: 'TSH-S', name: 'Small', price: 49.99 }],
})
// List with search, sort, pagination
const products = await admin.listProducts({
search: 'shirt',
sort: { field: 'createdAt', direction: 'desc' },
page: 1,
limit: 20,
})
// Update and delete
await admin.updateProduct('prod-1', { price: 39.99 })
await admin.deleteProduct('prod-1')
Categories
await admin.createCategory({ name: 'Electronics', nameAr: 'إلكترونيات' })
await admin.updateCategory('cat-1', { sortOrder: 2 })
await admin.deleteCategory('cat-1')
Orders (admin view)
// List all orders with filters
const orders = await admin.listOrders({
status: 'pending',
dateFrom: '2025-01-01',
search: 'order-123',
})
// Fulfill an order
await admin.fulfillOrder('order-1', {
trackingNumber: 'TR-12345',
trackingUrl: 'https://tracking.example.com/TR-12345',
})
// Refund an order
await admin.refundOrder('order-1', 'Customer requested refund')
Customers (admin view)
const customers = await admin.listCustomers({ search: 'ahmed' })
const customer = await admin.getCustomer('cust-1')
await admin.deleteCustomer('cust-1')
Store Settings
const settings = await admin.getStoreSettings()
await admin.updateStoreSettings({
name: 'My Store',
nameAr: 'متجري',
currency: 'SAR',
contactEmail: 'hello@mystore.com',
})
Inventory
await admin.updateInventory({
productId: 'prod-1',
quantity: 50,
adjustment: 'set', // 'set' | 'increment' | 'decrement'
})
const lowStock = await admin.getLowStockProducts(10) // threshold
Dashboard Stats
const stats = await admin.getDashboardStats()
// {
// totalProducts: 42,
// activeProducts: 38,
// totalOrders: 156,
// totalRevenue: 12500.00,
// totalCustomers: 89,
// recentOrders: [...],
// ordersByStatus: { pending: 5, completed: 140, cancelled: 11 }
// }
Profile System
The platform includes a cross-merchant buyer identity system for Commerce.js Cloud. Profiles exist independently of store-specific customer accounts.
import { createProfileDomain } from '@commercejs/platform'
const profiles = createProfileDomain()
// Lookup by email or phone
const profile = await profiles.lookupByEmail('buyer@example.com')
const profile = await profiles.lookupByPhone('+966500000000')
// CRUD
const newProfile = await profiles.createProfile({
email: 'buyer@example.com',
phone: '+966500000000',
firstName: 'Ahmed',
})
await profiles.updateProfile(profile.id, { lastName: 'Al-Rashid' })
// Addresses
await profiles.addAddress(profile.id, {
firstName: 'Ahmed', lastName: 'Al-Rashid',
street: 'King Fahd Rd', city: 'Riyadh', country: 'SA',
})
// Saved payment methods
await profiles.addPaymentMethod(profile.id, {
provider: 'tap', type: 'card',
last4: '4242', brand: 'Visa',
})
// Multi-merchant linking
await profiles.linkMerchant(profile.id, 'merchant-123', 'cust_abc')
OTP Verification
The platform exports OTP helpers for profile verification:
import { createOtpCode, findActiveOtpCode, markOtpVerified } from '@commercejs/platform'
// Create and send OTP
const otp = await createOtpCode({ profileId: profile.id, channel: 'sms' })
// Verify
const active = await findActiveOtpCode(profile.id)
if (active && active.code === userInput) {
await markOtpVerified(active.id)
}
Domain Factories
For building custom backends, individual domain factories are exported for standalone use:
import {
createCatalogDomain,
createCartDomain,
createCheckoutDomain,
createOrdersDomain,
createProfileDomain,
} from '@commercejs/platform'
const catalog = createCatalogDomain('SAR')
const products = await catalog.getProducts({ limit: 10 })
Seed Data
The seedDrizzle() function populates the database with demo data including:
- 1 store config (CommerceJS Demo Store, SAR currency, en/ar locales)
- 3 categories (Clothing, Electronics, Accessories)
- 3 products with images and variants
- 3 brands with bilingual names
- 6 GCC countries with calling codes
- 6 product reviews with varied ratings
import { seedDrizzle } from '@commercejs/platform'
await seedDrizzle()
Bilingual Support
All entity names support en and ar via LocalizedString:
const brands = await adapter.getBrands()
console.log(brands[0].name)
// { en: 'CommerceJS Essentials', ar: 'أساسيات كوميرس' }
Review Distribution
The getReviewSummary() method computes actual star distribution from review data:
const summary = await adapter.getReviewSummary('prod-1')
// {
// productId: 'prod-1',
// averageRating: 4.7,
// totalCount: 3,
// distribution: [0, 0, 0, 1, 2] // [1★, 2★, 3★, 4★, 5★]
// }
Testing
pnpm vitest run
Tests cover all implemented domains including seed data validation, CRUD operations, pagination, and error handling.