Primary reference: https://cap.cloud.sap/docs/guides/multitenancy/ MTX Services: https://cap.cloud.sap/docs/guides/multitenancy/mtxs
Setup with @sap/cds-mtxs (built-in MTX, recommended)
npm install @sap/cds-mtxs
CDS 10:
@sap/cds-mtxsis now v4.x. The old@sap/cds-mtxpackage and thecds migratemigration command have been removed — if you haven't migrated yet, use an older@sap/cds-mtxsv3.x to perform the migration first, then upgrade to v4.
npm install @sap/cds-mtxs # installs v4.x with CDS 10
package.json:
{
"cds": {
"profile": "with-mtx-sidecar",
"requires": {
"multitenancy": true,
"extensibility": true,
"toggles": true,
"db": {
"kind": "hana-mt"
},
"auth": {
"kind": "xsuaa"
}
}
}
}
xs-security.json for multitenancy
{
"xsappname": "my-cap-app",
"tenant-mode": "shared",
"scopes": [
{ "name": "$XSAPPNAME.mtcallback", "grant-as-authority-to-apps": ["$XSAPPNAME!t*"] },
{ "name": "$XSAPPNAME.mtdeployment", "description": "Scope to deploy to tenants" }
],
"authorities": ["$ACCEPT_GRANTED_AUTHORITIES"]
}
MTX Sidecar (for larger applications)
Add a separate sidecar module in mta.yaml:
- name: my-cap-app-mtx
type: nodejs
path: mtx/sidecar
build-parameters:
builder: npm
build-result: .
parameters:
memory: 512M
requires:
- name: my-cap-app-uaa
- name: my-cap-app-registry
- name: my-cap-app-db # service manager, not hdi-container!
mtx/sidecar/package.json:
{
"name": "my-cap-app-mtx",
"dependencies": { "@sap/cds": "*", "@sap/cds-mtxs": "*" },
"cds": {
"profile": "mtx-sidecar"
}
}
Tenant lifecycle hooks
// srv/provisioning-service.js
const cds = require('@sap/cds')
cds.on('mtx.tenant.provisioned', async ({ tenant }) => {
console.log(`Tenant ${tenant} has been onboarded`)
// Seed initial data, set up defaults, send welcome email, etc.
})
cds.on('mtx.tenant.deprovisioned', async ({ tenant }) => {
console.log(`Tenant ${tenant} has been offboarded`)
// Archive data, notify admins
})
cds.on('mtx.tenant.upgraded', async ({ tenant }) => {
console.log(`Tenant ${tenant} schema upgraded`)
})
Tenant-specific configuration
// In a handler, always scope to the current tenant
async onReadConfig(req) {
const { tenant } = req
const config = await SELECT.one(TenantConfig).where({ tenant_ID: tenant })
return config
}
Never use a single global config — always filter by req.tenant.
Extensibility: tenant-specific CDS extensions
Tenants can extend the base model (if extensibility: true):
// Tenant extension (uploaded via MTX API)
extend entity Products with {
tenantField : String(100);
}
Deploy tenant extension:
cds push --to https://my-app-mtx.cfapps.eu10.hana.ondemand.com \
--user alice@tenant.example.com
Service Manager (replaces HDI containers per tenant)
- name: my-cap-app-db
type: org.cloudfoundry.managed-service
parameters:
service: service-manager
service-plan: container
CAP's hana-mt database kind auto-provisions one HDI container per tenant via Service Manager.
Local multitenancy testing
{
"cds": {
"requires": {
"multitenancy": true,
"db": { "kind": "sqlite" },
"auth": {
"kind": "mocked",
"users": {
"alice": { "tenant": "tenant-A", "roles": ["admin"] },
"bob": { "tenant": "tenant-B", "roles": ["viewer"] }
}
}
}
}
}
CAP creates separate in-memory SQLite DBs per tenant automatically.
Common mistakes to avoid
- ❌ Using
hdi-containerinstead ofservice-manager— hdi-container is single-tenant only - ❌ Storing shared (cross-tenant) data in a tenant HDI container
- ❌ Hardcoding
tenant-mode: dedicatedin xs-security.json for a shared SaaS app - ❌ Forgetting
$ACCEPT_GRANTED_AUTHORITIESin xs-security.json — breaks MTX callback - ❌ Not seeding initial data in the
mtx.tenant.provisionedhook - ❌ Running schema upgrades manually — always use the MTX upgrade API