Multi-Adapter Composition
CommerceJS supports composing domains from different sources — Shopify for products, a custom CRM for customers, the built-in platform engine for cart/checkout. This guide walks through three composition strategies.
Strategy 1: Single Adapter
The simplest setup — one adapter handles everything:
import { createCommerce } from '@commercejs/core'
import { SallaAdapter } from '@commercejs/adapter-salla'
const commerce = createCommerce({
adapter: new SallaAdapter({ token: '...' }),
})
The adapter's capabilities array determines which domains are available.
Strategy 2: Composite Orchestrator
Use createCompositeOrchestrator() when different domains should come from different sources:
import { createCompositeOrchestrator } from '@commercejs/core'
import { SallaAdapter } from '@commercejs/adapter-salla'
import { PlatformAdapter } from '@commercejs/platform'
const salla = new SallaAdapter({ token: '...' })
const platform = new PlatformAdapter({ db: './store.db' })
const orchestrator = createCompositeOrchestrator({
name: 'hybrid-store',
providers: {
// Products from Salla
catalog: salla,
store: salla,
// Cart and checkout from built-in engine
cart: platform,
checkout: platform,
orders: platform,
customers: platform,
},
})
const commerce = createCommerce({ adapter: orchestrator })
Each domain slot is independently assignable. The orchestrator merges capabilities from all providers.
Strategy 3: Platform Fallback
Use withPlatformFallback() when you want to automatically fill gaps in a primary adapter:
import { withPlatformFallback } from '@commercejs/core'
import { SallaAdapter } from '@commercejs/adapter-salla'
import { PlatformAdapter } from '@commercejs/platform'
const salla = new SallaAdapter({ token: '...' })
const platform = new PlatformAdapter({ db: './store.db' })
// Salla supports: catalog, store, orders, customers, reviews, brands, etc.
// Platform fills gaps: cart, checkout, wishlist, returns
const orchestrator = withPlatformFallback(salla, platform)
const commerce = createCommerce({ adapter: orchestrator })
Important rules:
- Universal domains (
catalog,store) always come from the primary adapter - Common and specialized domains use the primary if supported, otherwise fall back
- Capabilities are merged from both adapters
Combining Strategies
You can nest composition strategies:
import { createCompositeOrchestrator, withPlatformFallback } from '@commercejs/core'
// Step 1: Compose primary domains from multiple sources
const composite = createCompositeOrchestrator({
name: 'multi-source',
providers: {
catalog: shopifyAdapter,
store: shopifyAdapter,
customers: crmAdapter,
},
})
// Step 2: Fill remaining gaps with platform engine
const orchestrator = withPlatformFallback(composite, platformAdapter)
// Result: catalog + store from Shopify, customers from CRM, everything else from Platform
const commerce = createCommerce({ adapter: orchestrator })
Adding Event-Driven Providers
Providers (notifications, analytics, tax) are configured alongside the adapter in createCommerce():
const commerce = createCommerce({
adapter: orchestrator,
payments: {
tap: new TapPaymentProvider({ secretKey: '...' }),
},
notifications: {
resend: resendProvider,
},
notificationRules: [
{
event: 'order.created',
channel: 'email',
provider: 'resend',
template: 'order_confirmation',
},
],
analytics: [ga4Provider],
})
All providers are event-driven — they subscribe to the event bus automatically when registered.
Checking Domain Support
At runtime, check what domains are available:
// Boolean check
if (commerce.hasCapability('wishlist')) {
const wishlist = await commerce.getWishlist()
}
// Type-safe domain access on orchestrator
if (orchestrator.supports('cart')) {
const cartAdapter = orchestrator.domain('cart')
}
// List all capabilities
console.log(orchestrator.capabilities)
// ['catalog', 'store', 'cart', 'checkout', 'orders', 'customers']