Configure cds-mtxs, understand per-tenant HDI containers, req.tenant routing, and the subscribe/unsubscribe lifecycle.
@sap/cds-mtxs plugin implements tenant isolationreq.tenant to scope data correctlyIf you build an application for multiple customer organisations that need to share the same deployment but keep their data strictly isolated, you need multitenancy. CAP's @sap/cds-mtxs (MTX Sidecar) handles the hard parts: per-tenant HANA HDI containers, automatic tenant routing, and a subscription API that BTP's SaaS Registry calls during onboarding. Without this, you would have to implement tenant routing, schema provisioning, and isolation yourself.
| Aspect | How CAP handles it |
|---|---|
| Data | Each tenant gets its own HANA HDI container |
| Authentication | JWT zid (zone ID) claim identifies the tenant |
| Routing | req.tenant is set automatically from the JWT |
| Schema upgrades | mtxs applies schema migrations to all tenant DBs |
| Onboarding | SaaS Registry calls your /mtx/v1/provisioning/tenant endpoint |
@sap/cds-mtxsnpm install @sap/cds-mtxs
// package.json — enable the plugin
{
"cds": {
"requires": {
"multitenancy": true,
"[production]": {
"auth": { "kind": "xsuaa" },
"db": { "kind": "hana" }
}
}
}
}
@sap/cds-mtxs automatically mounts a /mtx/v1 API for provisioning, model sync, and extensibility.
// xs-security.json
{
"xsappname": "my-cap-saas",
"tenant-mode": "shared", // CRITICAL: must be "shared" not "dedicated"
"scopes": [
{ "name": "$XSAPPNAME.user", "description": "Application user" },
{ "name": "$XSAPPNAME.admin", "description": "Admin" },
{ "name": "$XSAPPNAME.mtcallback", "description": "SaaS Registry callback" }
],
"oauth2-configuration": {
"token-validity": 43200,
"refresh-token-validity": 2592000
}
}
"tenant-mode": "shared" is essential — it enables the JWT zid claim that CAP uses to identify tenants.
req.tenant worksCAP reads the zid (zone ID) from the JWT and sets req.tenant automatically:
this.before('*', req => {
console.log('Tenant:', req.tenant); // e.g. 'abc-customer-guid'
// CQL queries are automatically scoped to the tenant's HDI container
// No WHERE clause needed — the DB connection is tenant-specific
});
You never have to add WHERE tenant = ? to your queries. The HDI container binding is resolved per tenant before the handler runs.
When a customer subscribes in BTP, the SaaS Registry calls:
PUT /mtx/v1/provisioning/tenant/{tenantId}
@sap/cds-mtxs handles this endpoint. It:
1. Creates a new HANA HDI container for the tenant
2. Deploys the current CDS schema to that container
3. Returns 200 OK when done
You can hook into the onboarding event:
// srv/provisioning.js
const cds = require('@sap/cds');
cds.on('served', async () => {
const { 'cds.xt.SaasProvisioningService': provisioning } = cds.services;
provisioning.on('subscribe', async ({ tenant, options }) => {
console.log(`Tenant ${tenant} subscribed`);
// Seed initial data for the new tenant
const db = await cds.connect.to('db', { tenant });
await db.insert(cds.entities.Settings).entries({
key: 'welcomeMessage', value: 'Welcome to CodeGoLive!'
});
});
provisioning.on('unsubscribe', async ({ tenant }) => {
console.log(`Tenant ${tenant} unsubscribed — cleaning up`);
// Optionally export or archive data before the HDI container is deleted
});
});
// .cdsrc.json — mock tenant setup
{
"requires": {
"auth": {
"kind": "mocked",
"users": {
"alice": { "tenant": "t1", "roles": ["admin"] },
"bob": { "tenant": "t2", "roles": ["user"] }
}
},
"multitenancy": true
}
}
cds watch
# alice (tenant t1) and bob (tenant t2) see completely isolated data
In-memory SQLite databases are used per tenant in local mock mode — no real HDI containers needed for development.
| Mistake | Fix |
|---|---|
"tenant-mode": "dedicated" in xs-security.json |
Must be "shared" for multitenancy to work |
| Hardcoding tenant ID in queries | Never — use req.tenant and let CAP route to the correct DB |
Not implementing the subscribe hook |
Default onboarding works, but you miss seeding initial data |
| Sharing a single HANA service instance | Each tenant needs its own HDI container — use hana/hdi-shared |
| Forgetting schema migrations apply to all tenants | cds-mtxs handles this, but unsafe schema changes affect all tenants simultaneously |
You understand the CAP multitenancy model — per-tenant HDI containers, req.tenant from the JWT zid claim, and the subscribe/unsubscribe lifecycle. You can configure @sap/cds-mtxs, set "tenant-mode": "shared" in XSUAA, hook into onboarding to seed initial data, and test tenant isolation locally with mocked users.