Packages

Platform

Built-in commerce engine — Neon Postgres, Admin API, Profile system, and full storefront adapter.

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

OptionTypeRequiredDefaultDescription
currencystringNo'SAR'Store currency code for price formatting
localestringNo'en'Default locale
connectionStringstringNoDATABASE_URL envNeon 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

DomainMethods
CataloggetProduct, getProducts, getCategories
CartcreateCart, getCart, addToCart, updateCartItem, removeFromCart, applyCoupon, removeCoupon
CheckoutgetShippingMethods, setShippingAddress, setBillingAddress, setShippingMethod, getPaymentMethods, setPaymentMethod, placeOrder
OrderscreateOrder, getOrder, getCustomerOrders, getOrderStatuses, updateOrderStatus, cancelOrder, duplicateOrder, getOrderHistory
Customerslogin, register, getCustomer, updateCustomer, logout, forgotPassword, resetPassword, getAddresses, addAddress, updateAddress, deleteAddress
StoregetStoreInfo

Extended domains

DomainMethods
BrandsgetBrands
CountriesgetCountries
WishlistgetWishlist, addToWishlist, removeFromWishlist
ReviewsgetProductReviews, getReviewSummary, submitReview
PromotionsgetActivePromotions, validateCoupon
ReturnscreateReturn, 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.