---
description: >
  Introducing the fundamental concepts of multitenancy, underpinning SaaS solutions in CAP. It describes how to run and test apps in multitenancy mode with minimized setup and overhead.
impl-variants: true
---

# Deploy Multitenant SaaS Applications

{{ $frontmatter.description }}

[[toc]]

## Introduction & Overview

CAP has built-in support for multitenancy with [the `@sap/cds-mtxs` package](https://www.npmjs.com/package/@sap/cds-mtxs).

Essentially, multitenancy is the ability to serve multiple tenants through single clusters of microservice instances, while strictly isolating the tenants' data. Tenants are clients using SaaS solutions.

In contrast to single-tenant mode, applications wait for tenants to subscribe before serving any end-user requests.

[Learn more about SaaS applications.](#about-saas-applications){.learn-more}



## Enable Multitenancy

Simply enable multitenancy for your CAP application using `cds add`, followed by an `npm install` for Node.js projects, or `mvn install` for Java projects, to install added package dependencies, like this:

::: code-group
```sh [Node.js]
cds add multitenancy
npm install
```

```sh [Java]
cds add multitenancy
mvn install
```
:::


::: details See what this adds to your **Node.js** project…

In case of **CAP Node.js** projects, the `cds add multitenancy` command...

1. Adds package dependency `@sap/cds-mtxs` to your project:

  ```jsonc
  {
    "dependencies": {
        "@sap/cds-mtxs": "^4"
    },
  }
  ```

2. Adds this configuration to your _package.json_ to enable multitenancy with sidecar:

  ```jsonc
  {
    "cds": {
      "profile": "with-mtx-sidecar",
      "requires": {
        "[production]": {
          "multitenancy": true
        },
        "[with-mtx]": {
          "multitenancy": true
        }
      }
    }
  }
  ```

3. Adds a sidecar subproject at `mtx/sidecar` with this _package.json_:

  ```json
  {
    "name": "bookshop-mtx",
    "dependencies": {
      "@cap-js/hana": "^3",
      "@sap/cds": "^10",
      "@sap/cds-mtxs": "^4",
      "@sap/xssec": "^4",
    },
    "devDependencies": {
      "@cap-js/sqlite": "^3"
    },
    "engines": {
      "node": ">=24"
    },
    "scripts": {
      "start": "cds-serve"
    },
    "cds": {
      "profile": "mtx-sidecar"
    }
  }
  ```

4. If necessary, modifies deployment descriptors such as `mta.yaml` for Cloud Foundry and Helm charts for Kyma.
:::




::: details See what this adds to your **Java** project…

In case of **CAP Java** projects, the `cds add multitenancy` command...

   1. Adds the following to _.cdsrc.json_ in your app:

      ```jsonc
      {
        "profile": "with-mtx-sidecar",
        "requires": {
          "[production]": {
            "multitenancy": true
          },
          "[with-mtx]": {
            "multitenancy": true
          }

        }
      }
      ```

   2. Adds the following to your _srv/pom.xml_ in your app:

       ```xml
       <dependency>
           <groupId>com.sap.cds</groupId>
           <artifactId>cds-feature-mt</artifactId>
           <scope>runtime</scope>
       </dependency>

		   <dependency>
		   	<groupId>org.xerial</groupId>
		   	<artifactId>sqlite-jdbc</artifactId>
		   	<scope>runtime</scope>
		   </dependency>
       ```

   3. Adds the following to your _srv/src/main/resources/application.yaml_:

       ```yml
       ---
       spring:
         config.activate.on-profile: with-mtx
       cds:
         multi-tenancy:
           sidecar.url: http://localhost:4005/ # in production mode overwrite with the URL from mta.yaml
       ```

   4. Adds a sidecar subproject at `mtx/sidecar` with this _package.json_:

      ```json
      {
        "name": "bookshop-mtx",
        "dependencies": {
          "@cap-js/hana": "^3",
          "@sap/cds": "^10",
          "@sap/cds-mtxs": "^4",
          "@sap/xssec": "^4"
        },
        "devDependencies": {
          "@cap-js/sqlite": "^3"
        },
        "engines": {
          "node": ">=24"
        },
        "scripts": {
          "start": "cds-serve",
          "build": "cds build ../.. --for mtx-sidecar --production && npm ci --prefix gen"
        },
        "cds": {
          "profiles": [
            "mtx-sidecar",
            "java"
          ]
        }
      }
      ```
:::

::: details Profile-based configuration presets

   The profiles `with-mtx-sidecar` and `mtx-sidecar` activate pre-defined configuration presets, which are defined as follows:

   ```js
   {
     "[with-mtx-sidecar]": { // [!code focus]
       requires: {
         db: {
           '[development]': {
              kind: 'sqlite',
              credentials: { url: 'db.sqlite' },
              schema_evolution: 'auto',
            },
           '[production]': {
              kind: 'hana',
              'deploy-format': 'hdbtable',
              'vcap': {
                'label': 'service-manager'
              }
            },
         },
         "[java]": {
           "cds.xt.ModelProviderService": { kind: 'rest', model:[] },
           "cds.xt.DeploymentService": { kind: 'rest', model:[] },
         },
         "cds.xt.SaasProvisioningService": false,
         "cds.xt.DeploymentService": false,
         "cds.xt.ExtensibilityService": false,
       }
     },
     "[mtx-sidecar]": { // [!code focus]
       requires: {
         db: {
           "[development]": {
              kind: 'sqlite',
              credentials: { url: "../../db.sqlite" },
              schema_evolution: 'auto',
            },
           "[production]": {
              kind: 'hana',
              'deploy-format': 'hdbtable',
              'vcap': {
                'label': 'service-manager'
              }
            },
         },
         "cds.xt.ModelProviderService": {
           "[development]": { root: "../.." }, // sidecar is expected to reside in ./mtx/sidecar
           "[production]": { root: "_main" },
           "[prod]": { root: "_main" } // for simulating production in local tests
         },
         "cds.xt.SaasProvisioningService": true,
         "cds.xt.DeploymentService": true,
         "cds.xt.ExtensibilityService": true,
       },
       "[development]": {
         server: { port: 4005 }
       }
     },
     …
   }
   ```

  ::: tip Inspect configuration
  You can always inspect the _effective_ configuration with `cds env`.
  :::


## Test-Drive Locally


Before deploying to the cloud, you can test-drive common SaaS operations with your app locally, including SaaS startup, subscribing tenants, and upgrading tenants.


::: details Additional configuration required for **CAP Java** projects…

  In case of **CAP Java** projects you need additional dependencies in the _pom.xml_ of the `srv` directory. To support mock users in the local test scenario add `cds-starter-cloudfoundry`:

  ```xml
  <dependency>
    <groupId>com.sap.cds</groupId>
    <artifactId>cds-starter-cloudfoundry</artifactId>
  </dependency>
  ```

  Then you add additional mock users to the spring-boot profile:

  ::: code-group

  ```yaml [application.yaml]
  ---
  spring:
    config.activate.on-profile: with-mtx
  #...
  cds:
    multi-tenancy:
      mtxs.enabled: true
    security.mock.users: # [!code focus]
      alice: # [!code focus]
        tenant: t1
        roles: [ admin ]
      bob: # [!code focus]
        tenant: t1
        roles: [ cds.ExtensionDeveloper ]
      erin: # [!code focus]
        tenant: t2
        roles: [ admin, cds.ExtensionDeveloper ]
  ```
:::


### 1. Start MTX Sidecar

In a first terminal, start the MTX sidecar process:

   ```sh
   cds watch mtx/sidecar
   ```

   ::: details  Inspecting the log output...

   In the trace output, we see several MTX services being served; most interesting for multitenancy: the _ModelProviderService_ and the _DeploymentService_.

   ```log
   [cds] - connect using bindings from: { registry: '~/.cds-services.json' }
   [cds] - connect to db > sqlite { url: '../../db.sqlite' }
   [cds] - serving cds.xt.ModelProviderService { path: '/-/cds/model-provider' } // [!code focus]
   [cds] - serving cds.xt.DeploymentService { path: '/-/cds/deployment' } // [!code focus]
   [cds] - serving cds.xt.SaasProvisioningService { path: '/-/cds/saas-provisioning' }
   [cds] - serving cds.xt.ExtensibilityService { path: '/-/cds/extensibility' }
   [cds] - serving cds.xt.JobsService { path: '/-/cds/jobs' }
   ```

   In addition, we can see a [`t0` tenant]./mtxs#about-technical-tenant-t0) being deployed, which is used by the MTX services for book-keeping tasks.

   ```log
   [cds] - loaded model from 1 file(s):

     ../../db/t0.cds

   [mtx|t0] - (re-)deploying SQLite database for tenant: t0 // [!code focus]
   /> successfully deployed to db-t0.sqlite // [!code focus]
   ```

   With that, the server waits for tenant subscriptions, listening on port 4005 by default in development mode.

   ```log
   [cds] - server listening on { url: 'http://localhost:4005' } // [!code focus]
   [cds] - launched at 3/5/2023, 1:49:33 PM, version: 7.0.0, in: 1.320s
   [cds] - [ terminate with ^C ]
   ```

   [If you get an error on server start, read the troubleshooting information.](../../get-started/get-help#why-do-i-get-an-error-on-server-start){.learn-more}
   :::


### 2. Launch the app server

In a second terminal, start the main CAP application server:

::: code-group
   ```sh [Node.js]
   cds watch --with-mtx
   ```
  ```sh [Java]
  mvn cds:watch -Dspring-boot.run.profiles=with-mtx
  ```
:::

::: details  Launched with shared database...

   The server starts as usual, but automatically uses a persistent database shared with the MTX sidecar instead of an in-memory one, as we can see in the trace output:

::: code-group
   ```log [Node.js]
   [cds] - loaded model from 6 file(s):

     db/schema.cds
     srv/admin-service.cds
     srv/cat-service.cds
     srv/user-service.cds
     ../../../cds-mtxs/srv/bootstrap.cds
     ../../../cds/common.cds

   [cds] - connect using bindings from: { registry: '~/.cds-services.json' }
   [cds] - connect to db > sqlite { url: 'db.sqlite' } // [!code focus]
   [cds] - serving AdminService { path: '/odata/v4/admin', impl: 'srv/admin-service.js' }
   [cds] - serving CatalogService { path: '/odata/v4/catalog', impl: 'srv/cat-service.js' }
   [cds] - serving UserService { path: '/user', impl: 'srv/user-service.js' }

   [cds] - server listening on { url: 'http://localhost:4004' }
   [cds] - launched at 3/5/2023, 2:21:53 PM, version: 6.7.0, in: 748.979ms
   [cds] - [ terminate with ^C ]
   ```
  ```log [Java]
  2023-03-31 14:19:23.987  INFO 68528 --- [  restartedMain] c.s.c.bookshop.Application               : The following 1 profile is active: "with-mtx"
  ...
  2023-03-31 14:19:23.987  INFO 68528 --- [  restartedMain] c.s.c.services.impl.ServiceCatalogImpl   : Registered service ExtensibilityService$Default
  2023-03-31 14:19:23.999  INFO 68528 --- [  restartedMain] c.s.c.services.impl.ServiceCatalogImpl   : Registered service CatalogService
  2023-03-31 14:19:24.016  INFO 68528 --- [  restartedMain] c.s.c.f.s.c.runtime.CdsRuntimeConfig     : Registered DataSource 'ds-mtx-sqlite'// [!code focus]
  2023-03-31 14:19:24.017  INFO 68528 --- [  restartedMain] c.s.c.f.s.c.runtime.CdsRuntimeConfig     : Registered TransactionManager 'tx-mtx-sqlite'// [!code focus]
  2023-03-31 14:19:24.554  INFO 68528 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
  2023-03-31 14:19:24.561  INFO 68528 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
  2023-03-31 14:19:24.561  INFO 68528 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.71]
  ```
:::


### 3. Subscribe Tenants

In the third terminal, subscribe and unsubscribe tenants as follows:

   ::: code-group
   ```sh [cds subscribe]
   cds subscribe t1 --to http://localhost:4005
   cds subscribe t2 --to http://localhost:4005
   ```
   ```sh [cds unsubscribe]
   cds unsubscribe t1 --from http://localhost:4005
   cds unsubscribe t2 --from http://localhost:4005
   ```
   :::

  ::: details  Behind the scenes...

   The `cds subscribe` command sends HTTP requests to the MTX sidecar's `SaasProvisioningService` as follows:

   ```http [HTTP]
   POST http://localhost:4005/-/cds/saas-provisioning/subscribe HTTP/1.1
   Content-Type: application/json
   Authorization: Basic yves:
   { "tenant": "t1" }
   ```
   A programmatic call from a CAP Node.js client would look like this:
   ```js [JavaScript]
   const ds = await cds.connect.to ('cds.xt.SaasProvisioningService')
   await ds.subscribe('t1')
  ```
  Upon receiving a subscription request, the sidecar creates a new persistent tenant database per tenant, hence keeping tenant data isolated:

  ```log
  [cds] - POST /-/cds/deployment/subscribe
  [mtx] - (re-)deploying SQLite database for tenant: t1 // [!code focus]
  > init from db/init.js // [!code focus]
  > init from db/data/sap.capire.bookshop-Authors.csv // [!code focus]
  > init from db/data/sap.capire.bookshop-Books.csv // [!code focus]
  > init from db/data/sap.capire.bookshop-Books_texts.csv // [!code focus]
  > init from db/data/sap.capire.bookshop-Genres.csv // [!code focus]
  /> successfully deployed to ./../../db-t1.sqlite  // [!code focus]

  [mtx] - successfully subscribed tenant t1
  ```
  :::


### 4. Test via the app's UI

For example, in case of [_@capire/bookshop_](https://github.com/capire/bookshop) sample, you can now test your app with different users/tenants as follows...

Open the _Manage Books_ app at <http://localhost:4004/#Books-manage> and log in with `alice`. Select **Wuthering Heights** to open the details, edit here the title and save your changes. You've changed data in one tenant.

To see requests served in tenant isolation, that is, from different databases, check that it's not visible in the other one. Open a private/incognito browser window and log in as `erin` to see that the title still is _Wuthering Heights_.

In the following example, _Wuthering Heights (only in t1)_ was changed by _alice_.  _erin_ doesn't see it, though.

![A screenshot of the bookshop application showing the effect of tenant isolation logged in as _alice_, as described in the previous sentence.](assets/book-changed-t1.png){style="width: 450px; box-shadow: 1px 1px 5px #888888"}

   ::: details Use private/incognito browser windows to test with different tenants...

   Do this to force new logins with different users, assigned to different tenants:

   1. Open a new _private_ / _incognito_ browser window.
   2. Open <http://localhost:4004/#Books-manage> in it &rarr; log in as `alice`.
   3. Repeat that with `erin`, another pre-defined user, assigned to tenant `t2`.

   :::

   ::: details Note tenants displayed in trace output...

   We can see tenant labels in server logs for incoming requests:

   ```log
   [cds] - server listening on { url: 'http://localhost:4004' }
   [cds] - launched at 3/5/2023, 4:28:05 PM, version: 6.7.0, in: 736.445ms
   [cds] - [ terminate with ^C ]

   ...
   [odata|t1] - POST /adminBooks { '$count': 'true', '$select': '... } // [!code focus]
   [odata|t2] - POST /adminBooks { '$count': 'true', '$select': '... }  // [!code focus]
   ...
   ```

   :::

   ::: details Pre-defined users in `mocked-auth`

   How users are assigned to tenants and how tenants are determined at runtime largely depends on your identity providers and authentication strategies. The `mocked` authentication strategy, used by default with `cds watch`, has a few [pre-defined users](../../node.js/authentication#mock-users) configured. You can inspect these by running `cds env requires.auth`:

   ```js
   [bookshop] cds env requires.auth
   {
    kind: 'basic-auth',
    strategy: 'mock',
    users: {
      alice: { tenant: 't1', roles: [ 'admin' ] },
      bob:   { tenant: 't1', roles: [ 'cds.ExtensionDeveloper' ] },
      carol: { tenant: 't1', roles: [ 'admin', 'cds.ExtensionDeveloper' ] },  // [!code focus]
      dave:  { tenant: 't1', roles: [ 'admin' ], features: [] },
      erin:  { tenant: 't2', roles: [ 'admin', 'cds.ExtensionDeveloper' ] },  // [!code focus]
      fred:  { tenant: 't2', features: ... },
      me:    { tenant: 't1', features: ... },
      yves:  { roles: [ 'internal-user' ] }
      '*':     true //> all other logins are allowed as well
    },
    tenants: { t1: { features: … }, t2: { features: '*' } }
   }
   ```

   You can also add or override users or tenants by adding something like this to your _package.json_:

   ```jsonc
   "cds":{
    "requires": {
      "auth": {
        "users": {
          "u2": { "tenant": "t2" }, // [!code focus]
          "u3": { "tenant": "t3" } // [!code focus]
        }
      }
    }
   }
   ```

   :::

### 5. Upgrade Your Tenant

When deploying new versions of your app, you also need to upgrade your tenants' databases. For example, open `db/data/sap.capire.bookshop-Books.csv` and add one or more entries in there. Then upgrade tenant `t1` as follows:

   ::: code-group

   ```sh [CLI]
   cds upgrade t1 --at http://localhost:4005 -u yves:
   ```

   ```http
   POST http://localhost:4005/-/cds/deployment/upgrade HTTP/1.1
   Content-Type: application/json
   Authorization: Basic yves:

   { "tenant": "t1" }
   ```

   ```js [JavaScript]
   const ds = await cds.connect.to('cds.xt.DeploymentService')
   await ds.upgrade('t1')
   ```

   :::

> After that, open or refresh <http://localhost:4004/#Books-manage> again as _alice_ and _erin_ &rarr; the added entries are visible for _alice_, but still missing for _erin_, as `t2` has not yet been upgraded.


## Deploy to Cloud

In order to get your multitenant application deployed, follow this excerpt from the [deployment to CF](../deploy/to-cf) and [deployment to Kyma](../deploy/to-kyma) guides.

1. Prepare your application for production, **once**:
    ::: code-group
    ```sh [Cloud Foundry]
    cds add hana,xsuaa  # add required production services
    cds add portal      # as an option to serve UIs, if any
    cds add mta         # to enable MTA based deployments
    ```
    ```sh [Kyma]
    cds add hana,xsuaa  # add required production services
    cds add portal      # as an option to serve UIs, if any
    cds add kyma        # to enable Kyma/Helm based deployments
    ```
    :::

2. Deploy the application:
    ```sh
    cds up
    ```

:::tip For manual setups, ensure the metadata container (`t0`) is unique

If you’re not running [`cds-mtx upgrade *`](#update-database-schema) as a Cloud Foundry hook (as set up by `cds add multitenancy`) and instead use a custom setup, deploy the MTX sidecar with a single instance for the initial rollout. This avoids conflicts when `t0` is created.

:::



### Subscribe via BTP Cockpit

**Create a BTP subaccount** to subscribe to your deployed application. This subaccount has to be in the same region as the provider subaccount, for example, `us10`.

See the [list of all available regions](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/f344a57233d34199b2123b9620d0bb41.html). {.learn-more}

![Global Account view to create a subaccount.](assets/create-subaccount.png){.mute-dark}

In your **subscriber account** go to _Instances and Subscription_ and select _Create_.

![The screenshot is explained in the accompanying text.](assets/sub-account.png){.mute-dark}

Select _bookshop_ and use the only available plan _default_.

![The screenshot is explained in the accompanying text.](assets/subscribe-bookshop.png){.mute-dark}

[Learn more about subscribing to a SaaS application using the SAP BTP cockpit.](https://help.sap.com/docs/btp/sap-business-technology-platform/subscribe-to-multitenant-applications-using-cockpit?version=Cloud#procedure){.learn-more}
[Learn more about subscribing to a SaaS application using the `btp` CLI.](https://help.sap.com/docs/btp/btp-cli-command-reference/btp-subscribe-accounts-subaccount?locale=en-US){.learn-more}

You can now access your subscribed application via _Go to Application_.

![The screenshot is explained in the accompanying text.](assets/go-to-app.png){.mute-dark}

As you can see, your route doesn't exist yet. You need to create and map it first.

> If you're deploying to Kyma, your application will load and you won't get the below error. You can skip the step of exposing the route.

```log
404 Not Found: Requested route ('...') does not exist.
```

> Leave the window open. You need the information to create the route.

#### Cloud Foundry

Use the following command to create and map a route to your application:

```sh
cf map-route ‹app› ‹paasDomain› --hostname ‹subscriberSubdomain›-‹saasAppName›
```

In our example, let's assume our `saas-registry` is configured in the _mta.yaml_ like this:

```yaml
- name: bookshop-registry
  type: org.cloudfoundry.managed-service
  parameters:
    service: saas-registry
    service-plan: application
    config:
      appName: bookshop-${org}-${space} # [!code focus]
```

Let's also assume we've deployed to our app to Cloud Foundry org `myOrg` and space `mySpace`. This would be the full command to create a route for the subaccount with subdomain `subscriber1`:

```sh
cf map-route bookshop cfapps.us10.hana.ondemand.com --hostname subscriber1-myOrg-mySpace-bookshop
```

::: details Learn how to do this in the BTP cockpit instead…

Switch to your **provider account** and go to your space → Routes. Click on _New Route_.

![The screenshot is explained in the accompanying text.](assets/cockpit-routes.png){.mute-dark}

Here, you need to enter a _Domain_ and _Host Name_.

![The screenshot is explained in the accompanying text.](assets/cockpit-routes-new.png){.mute-dark}

Let's use this route as example:

_<https://subscriber1-bookshop.cfapps.us10.hana.ondemand.com>_

- The **Domain** here is _cfapps.us10.hana.ondemand.com_
- The **Host Name** here is _subscriber1-bookshop_

Hit _Save_ to create the route.

You can now see the route is created but not mapped to an application yet.

![The screenshot is explained in the accompanying text.](assets/cockpit-routes-new-overview.png){.mute-dark}

Click on _Map Route_, choose your App Router module and hit _Save_.

![The screenshot is explained in the accompanying text.](assets/cockpit-routes-new-map.png){.mute-dark}

You should now see the route mapped to your application.

![Overview in your dev space with the newly mapped route.](assets/cockpit-routes-new-mapped-overview.png){.mute-dark}

:::

### Update Database Schema

There are several ways to update the database schema of a multitenant application.

* For **CAP Java** applications, schema updates should be done as described in the respective [Java Guide](../../java/multitenancy#database-update).
* For **CAP Node.js** applications, you can use either of the following as shown in the examples below:
  - the `cds-mtx upgrade` command from a terminal
  - the [MTX Sidecar API](mtxs#upgrade-tenants--jobs)
  - via a [CloudFoundry hook](https://help.sap.com/docs/btp/sap-business-technology-platform/module-hooks)
  - via a [CloudFoundry task](https://tutorials.cloudfoundry.org/cf4devs/advanced-concepts/tasks/)
  - via a [Kubernetes job](https://kubernetes.io/docs/concepts/workloads/controllers/job/)


::: code-group
```sh [cds-mtx upgrade]
cd mtx/sidecar
cds-mtx upgrade t1 # single tenants
cds-mtx upgrade \* # all tenants
```
```http [MTX Sidecar API]
POST /-/cds/saas-provisioning/upgrade HTTP/1.1
Content-Type: application/json

{ "tenants": ["t1"] }
```
```yaml [CF hook]
# mta.yaml
hooks:
  - name: upgrade-all
    type: task
    phases:
      - blue-green.application.before-start.idle
      - deploy.application.before-start
    parameters:
      name: upgrade
      memory: 512M
      disk-quota: 768M
      command: cds-mtx upgrade '*'
```
```sh [CF task]
cf run-task ‹app› --name "upgrade-all" --command "cds-mtx upgrade '*'"
```
```yaml [Kubernetes job]
# values.yaml
mtx-upgrade:
  bindings:
    saas-registry: # when using XSUAA
      serviceInstanceName: saas-registry
    subscription-manager: # when using IAS
      serviceInstanceName: subscription-manager
    service-manager:
      serviceInstanceName: service-manager
  image:
    repository: bookshop-sidecar
  resources:
    limits:
      ephemeral-storage: 1G
      memory: 1G
    requests:
      ephemeral-storage: 1G
      cpu: 1000m
      memory: 1G
  command: ["launcher"]
  args:
    - "cds-mtx"
    - "upgrade"
    - '*'
```
:::

::: info Managing large upgrade workloads

Very large projects might need to increase resources or limit parallelism of tenant upgrades.

[The best practice algorithm is laid out in our _Get Help_ guide.](../../get-started/get-help#why-is-my-mtx-sidecar-is-killed-with-exit-status-137){.learn-more}{style="margin-top:10px"}

:::


### Test-Drive in Hybrid Setup

You can run the app locally while binding it to remote service instances created by a Cloud Foundry deployment.
To do so, use `cds bind` to bind your SaaS app and the MTX sidecar to its required cloud services, like that:

```sh
cds bind -a bookshop-srv
```

For testing the sidecar, make sure to run the command there as well:

```sh
cd mtx/sidecar
cds bind -a bookshop-mtx
```

To generate the SAP HANA HDI files for deployment, go to your project root and run the build:

```sh
cds build --production
```

::: warning Run `cds build` after model changes
Each time you update your model or any SAP HANA source file, you need repeat the build.
:::

> Make sure to stop any running CAP servers left over from local testing.

By passing `--profile hybrid` you can now run the app with cloud bindings and interact with it as you would while [testing your app locally](#test-drive-locally). Run this in your project root:

```sh
cds watch mtx/sidecar --profile hybrid
```

Then, in another terminal, start the main application:

::: code-group
```sh [Node.js]
cds watch --profile hybrid
```
```sh [Java]
cd srv
mvn cds:watch -Dspring-boot.run.profiles=hybrid
```
:::


Learn more about [Hybrid Testing](../../tools/cds-bind).{.learn-more}

::: tip Manage multiple deployments
Use a dedicated profile for each deployment landscape if you are using several, such as `dev`, `test`, `prod`. For example, after logging in to your `dev` space:

```sh
cds bind -2 bookshop-db --profile dev
cds watch --profile dev
```
:::

###### sap-hana-tenant-management-service-v2 <!-- referenced from help portal -->

### SAP HANA TMS v2

The SAP HANA Tenant Management Service (TMS) v2 service provides direct support for managing SAP HANA tenants.

> [!important] Be aware of the current limitations:
> - **Not suitable for existing applications** as there is **no migration from Service Manager** available yet. This will be provided as HANA tool later.<br>
> There **won't be support for both Service Manager and TMS v2** together in one application.

[For more information, see the SAP HANA documentation](https://help.sap.com/docs/hana-cloud/sap-hana-cloud-multitenancy/introducing-sap-hana-cloud-multitenancy){.learn-more}
[Find the TMS v2 API on the SAP Business Accelerator Hub](https://api.sap.com/api/TenantAPI/overview){.learn-more}

#### Configure MTXS for Tenant Management Service

This documentation uses the bookshop sample to showcase necessary configuration.

If you start with a multitenant application that's configured to use to SAP HANA, you need to change the configuration of the database in your _mta.yaml_ file:
```yaml
- name: bookshop-db
  type: org.cloudfoundry.managed-service
  parameters:
    service: hana-cloud
    service-plan: hana-multitenancy
```

> [!danger] Only use the `hana-multitenancy` plan
> As the tenant containers are filtered by the SAP HANA Cloud service instance, applications will potentially access data of other applications when using a different plan.

For SAP HANA TMS v2, you also need to specify the database ID of the database that you plan to use for your tenant containers. You can specify this using the [`cds.xt.DeploymentService` configuration](./mtxs#deployment-config).

With `cds.xt.DeploymentService` you only configure the default database ID. If you want to specify different database IDs for a tenant HDI container, you need to [add the database ID to the payload of the individual subscription using a handler](./mtxs#example-handler-for-saasprovisioningservice).

To keep the application configuration agnostic, we recommend adding the <Config label="database_id" keyDelim="/">cds/requires/cds.xt.DeploymentService/hdi/create/database_id</Config> configuration as an environment variable to the MTX service in _mta.yaml_:
```yaml{6-17}
- name: bookshop-mtx
  type: nodejs
  path: gen/mtx/sidecar
  ...
  properties:
    CDS_CONFIG: |
      {
        "requires": {
          "cds.xt.DeploymentService": {
            "hdi": {
              "create": {
                "database_id": "4baa4d82-a474-4281-90af-67261c893590"
              }
            }
          }
        }
      }
```

To further separate deployment configuration, you can also use a separate [Deployment Extension Descriptor](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-developer-guide-for-cloud-foundry-multitarget-applications-sap-business-app-studio/mta-deployment-extension-descriptor) on Cloud Foundry.

You can also pass the configuration for each individual subscription as a payload extension in the request to the [SaasProvisioningService](./mtxs#subscription).

#### Handle SAP HANA Tenants

By default, MTXS creates a separate SAP HANA tenant for each subscriber tenant (BTP tenant).
In some scenarios, you might also want to use the same SAP HANA tenant for several applications or microservices.

The SAP HANA tenant ID must be unique across all SAP HANA database instances within a region. If
you intend to deploy your application multiple times in a region, ensure this uniqueness across deployments.

##### Mandatory: specify a unique prefix for the SAP HANA tenant name
Specify a <Config label="hana_tenant_prefix" keyDelim="/">cds/requires/cds.xt.DeploymentService/hdi/create/hana_tenant_prefix</Config> value that is unique for each **deployed application instance**:
```jsonc
"cds.xt.DeploymentService": {
  "hdi": {
    "create": {
      ...
      "hana_tenant_prefix": "<prefix>"
    }
  }
}
```
The resulting SAP HANA tenant name is constructed from `prefix`+`subscriber tenant`. The SAP HANA tenant ID
is generated as a hashed UUID from the SAP HANA tenant name, and the original name is added as a label.

![Shows the relations of a HANA Tenant](./assets/hana_tenants.drawio.svg)

To help ensure that the generated SAP HANA tenant UUID is unique within a region, you could build the
`hana_tenant_prefix` in your deployment descriptor using various variables, for example,
like `prefix-${org}-${space}` in Cloud Foundry.

:::warning Prefix is mandatory
The <Config label="hana_tenant_prefix" keyDelim="/" keyOnly>cds/requires/cds.xt.DeploymentService/hdi/create/hana_tenant_prefix</Config> configuration is mandatory to ensure that the internal tenant [`t0`](./mtxs#about-technical-tenant-t0) is created with its own SAP HANA tenant.
:::

:::warning Length restriction
The prefix and subscriber tenant name are each limited to 63 characters maximum.
:::

:::warning Do not use `hana_tenant_prefix` in queries
The `hana_tenant_prefix` is only used to generate the SAP HANA tenant UUID to avoid duplicates when creating an
SAP HANA tenant. Although it is added as `prefix` label to the SAP HANA tenant, it is not reliable and must not
be used for any query.
:::


##### Mandatory for CAP Java Applications
For **CAP Java** applications you need to set the same prefix in the <Config java>cds.multitenancy.hanaMtService.hanaTenantPrefix</Config> property. We recommend doing this using the environment variable in the _mta.yaml_:
```yaml
- name: srv
  type: java
  path: srv
  ...
  properties:
    CDS_MULTITENANCY_HANAMTSERVICE_HANATENANTPREFIX: "some-prefix"
```

##### Assign Many Tenant Containers to a Common SAP HANA Tenant

To group the tenant containers of many applications or microservices in a common HANA tenant, you need to make sure that the same SAP HANA tenant ID is used.

![One HANA tenant for many applications or microservices](./assets/hana_tenants_for_many.drawio.svg)

**Option 1: Configure the same `hana_tenant_prefix`**
: You can configure the same [`hana_tenant_prefix`](#mandatory-specify-a-unique-prefix-for-the-sap-hana-tenant-name) many applications or microservices. With that, the SAP HANA tenant ID is generated in the same way for each subscriber tenant.

: If you choose this option, the following preconditions need to be met
- The subscriber tenant ID (BTP tenant ID) needs to be the same for all applications or microservices.
- All applications or microservices need to use the [same database](#configure-mtxs-for-tenant-management-service) for the same subscriber tenant.

**Option 2: Pass the SAP HANA Tenant ID with a Subscription**

: **... in CAP Node.js**

   If you want to control the ID of the SAP HANA tenant ID on your own, you can pass it as subscription payload as parameters, for example, using a handler for the [`SaasRegistryService`](./mtxs#put-tenant):
   ```jsonc
   {
     "subscribedTenantId": "t1",
     "subscribedSubdomain": "subdomain1",
     "eventType": "CREATE"
     ...
     "_": {
   	  "hdi": {
   	    "create": {
   	      "hana_tenant_id": "5b3c0699-1c65-4ec1-9a8e-b7cfc3cc15bc"
   	    }
   	  }
     }
   }
   ```
  The `hana_tenant_id` must be a valid UUID and must be unique per subscriber tenant. Specifying `hana_tenant_id` overrides the prefix settings mentioned earlier,
  except for [the internal tenant t0](./mtxs#about-technical-tenant-t0). Also ensure that the ID is unique within a region.

:  **... in CAP Java**

  To specify the ID of the SAP HANA tenant in **CAP Java** applications you can register a custom handler for the `before`    phase of the `SUBSCRIBE` event that sets the `hana_tenant_id` within the provisioning parameters:

  ```java
  @Before
  public void beforeSubscription(SubscribeEventContext context) {
      context.getOptions().put("provisioningParameters",
          Collections.singletonMap("hana_tenant_id", "<ID>"));
  }
  ```

  This will affect every new [tenant subscription](../../java/multitenancy.md#subscribe-tenant) and will set the specified SAP HANA tenant ID.

<div id="tmscmk" />


##### Delete SAP HANA tenants

When you unsubscribe and the tenant container is deleted, the corresponding SAP HANA tenant isn't deleted as it could potentially still be in use for other applications.

#### Limitations

There are still some limitations with the current client implementation.

- **Database ID is Mandatory**
  As mentioned, you need to specify a database ID that's to be used, either for all tenants or per subscription request, see [Deployment configuration](./mtxs#deployment-config).


## SaaS Dependencies {#saas-dependencies}
Some of the xsuaa-based services your application consumes need to be registered as _reuse services_ to work in multitenant environments. This holds true for the usage of both the SaaS Registry service and the Subscription Manager Service (SMS).

CAP Java as well as `@sap/cds-mtxs`, each offer an easy way to integrate these dependencies. They support some services out of the box and also provide a simple API for applications. Most notably, you need such dependencies for the following SAP BTP services: [Audit Log](https://discovery-center.cloud.sap/serviceCatalog/audit-log-service), [Event Mesh](https://discovery-center.cloud.sap/serviceCatalog/event-mesh), [Destination](https://discovery-center.cloud.sap/serviceCatalog/destination-service), [HTML5 Application Repository](https://discovery-center.cloud.sap/serviceCatalog/html5-application-repository-service), and [Cloud Portal](https://discovery-center.cloud.sap/serviceCatalog/cloud-portal-service).

For CAP Java, all these services are supported natively and SaaS dependencies are automatically created if such a service instance is bound to the CAP Java application, that is, the `srv` module.

:::tip Explicitly activate the Destination service
SaaS dependency for Destination service needs to be activated explicitly in the `application.yaml` due to security reasons. SaaS dependencies for some of the other services can be **de**activated by setting the corresponding property to `false` in the `application.yaml`.

Refer to the `cds.multiTenancy.dependencies` section in the [CDS properties](../../java/developing-applications/properties#cds-properties).
:::

For CAP Node.js, all these services are supported natively and can be activated individually by providing configuration in `cds.requires`. In the most common case, you simply activate service dependencies like so:

::: code-group

```json [mtx/sidecar/package.json]
"cds": {
  "requires": {
    "audit-log": true,
    "connectivity": true,
    "destinations": true,
    "html5-repo": true,
    "portal": true
  }
}
```

:::

::: details Defaults provided by `@sap/cds-mtxs`...

The Boolean values in the _mtx/sidecar/package.json_ activate the default configuration in `@sap/cds-mtxs`:

```json
"cds": {
  "requires": {
    "connectivity": {
      // Uses credentials.xsappname
      "vcap": { "label": "connectivity" },
      "subscriptionDependency": "xsappname"
    },
    "portal": {
      "vcap": { "label": "portal" },
      // Uses credentials.uaa.xsappname
      "subscriptionDependency": {
        "uaa": "xsappname"
      }
    },
    ...
  }
}
```

:::

### Additional Services

In **CAP Java**, if your application uses a service that isn't supported out of the box, you can define dependencies by providing a custom handler.

[Learn more about defining dependent services](../../java/multitenancy#define-dependent-services){.learn-more}

In **CAP Node.js**, you can use a custom `subscriptionDependency` entry in your application's or CAP plugin's _package.json_:

```json [package.json]
"cds": {
  "requires": {
    "my-service": {
      "subscriptionDependency": "xsappname"
    }
  }
}
```

> The `subscriptionDependency` specifies the property name of the credentials value with the desired `xsappname`, starting from `cds.requires['my-service'].credentials`. Usually it's just `"xsappname"`, but JavaScript objects interpreted as a key path are also allowed, such as `{ "uaa": "xsappname" }` in the defaults example for `portal`.

Alternatively, overriding the [`dependencies`](./mtxs#get-dependencies) handler gives you full flexibility for any custom implementation.

<div id="subscriptiondashboard" />

## Adding Custom Handlers

[MTX services](mtxs.md) are implemented as standard CAP services, so you can add custom handlers to all respective lifecycle events just as you would for any application service. To do so simply add a `server.js` file in the _mtx/sidecar/_ folder, with content like this:

::: code-group
```js [mtx/sidecar/server.js]
cds.on('served', () => {
  const { 'cds.xt.DeploymentService': ds } = cds.services
  ds.before('subscribe', async (req) => {
    // HDI container credentials are not yet available here
    const { tenant } = req.data
  })
  ds.before('upgrade', async (req) => {
    // HDI container credentials are not yet available here
    const { tenant } = req.data
  })
  ds.after('deploy', async (result, req) => {
    const { container } = req.data.options
    const { tenant } = req.data
    ...
  })
  ds.after('unsubscribe', async (result, req) => {
    const { container } = req.data.options
    const { tenant } = req.data
  })
})
```
:::

[Learn more about that in the _MTX Services Reference_ documentation](./mtxs){.learn-more}



In case of **CAP Java** projects, you can alternatively add custom handlers to the main app as described in the [Java documentation](../../java/multitenancy#custom-logic):

```java
@After
private void subscribeToService(SubscribeEventContext context) {
   String tenant = context.getTenant();
   Map<String, Object> options = context.getOptions();
}

@On
private void upgradeService(UpgradeEventContext context) {
   List<String> tenants = context.getTenants();
   Map<String, Object> options = context.getOptions();
}

@Before
private void unsubscribeFromService(UnsubscribeEventContext context) {
   String tenant = context.getTenant();
   Map<String, Object> options = context.getOptions();
}
```
[Learn more about that in the _Java Multitenancy Guide_ documentation](../../java/multitenancy#custom-logic){.learn-more}


## Configuring the Java Service

In case of CAP Java projects, `cds add multitenancy` adds additional configuration similar to this:

::: code-group

```yaml [mta.yaml (Cloud Foundry)]
modules:
  - name: bookshop-srv
    type: java
    path: srv
    parameters:
      ...
    provides:
      - name: srv-api # required by consumers of CAP services (e.g. approuter)
        properties:
          srv-url: ${default-url}
    requires:
      - name: app-api
        properties:
          CDS_MULTITENANCY_APPUI_URL: ~{url}
          CDS_MULTITENANCY_APPUI_TENANTSEPARATOR: "-"
      - name: bookshop-auth
      - name: bookshop-db
      - name: mtx-api
        properties:
          CDS_MULTITENANCY_SIDECAR_URL: ~{mtx-url}
      - name: bookshop-registry
```

```yaml [values.yaml (Kyma)]
...
srv:
  bindings:
    ...
  image:
    repository: bookshop-srv
  env:
    SPRING_PROFILES_ACTIVE: cloud
    CDS_MULTITENANCY_APPUI_TENANTSEPARATOR: "-"
    CDS_MULTITENANCY_APPUI_URL: https://{{ .Release.Name }}-srv-{{ .Release.Namespace }}.{{ .Values.global.domain }}
    CDS_MULTITENANCY_SIDECAR_URL: https://{{ .Release.Name }}-sidecar-{{ .Release.Namespace }}.{{ .Values.global.domain }}
  ...
```
:::

In which the following environment variables are set:

| `CDS_MULTITENANCY_...` | Description |
|---------------------|-------------|
| `SIDECAR_URL` | Sets the application property <Config java>cds.multitenancy.sidecar.url</Config>. This URL is required by the CAP Java runtime to connect to the MTX Sidecar application and is derived from the property `mtx-url` of the mtx-sidecar module. |
| `APPUI_URL` | Sets the entry point URL that is shown in the SAP BTP Cockpit. |
| `APPUI_TENANTSEPARATOR` | The separator in generated tenant-specific URL. |

The tenant application requests are separated by the tenant-specific app URL:

```http
https://<subaccount subdomain><CDS_MULTITENANCY_APPUI_TENANTSEPARATOR><CDS_MULTITENANCY_APPUI_URL>
```

::: tip Use MTA extensions for landscape-specific configuration

You can define the environment variable `CDS_MULTITENANCY_APPUI_TENANTSEPARATOR` in an MTA extension descriptor:

::: code-group

```yaml [mt.mtaext]
_schema-version: "3.1"
extends: my-app
ID: my-app.id
modules:
  - name: srv
    properties:
      CDS_MULTITENANCY_APPUI_TENANTSEPARATOR: "-"
  - name: app
    properties:
      TENANT_HOST_PATTERN: ^(.*)-${default-uri}
```

[Learn more about _Defining MTA Extension Descriptors_](https://help.sap.com/docs/btp/sap-business-technology-platform/defining-mta-extension-descriptors?q=The%20MTA%20Deployment%20Extension%20Descriptor){.learn-more}


:::

#### Option: Provisioning Only

Under certain conditions it makes a lot of sense to use the MTX Sidecar only for tenant provisioning. This configuration can be used in particular when the application doesn't offer (tenant-specific) model extensions and feature toggles. In such cases, business requests can be served by the Java runtime without interaction with the sidecar, for example to fetch an extension model.

Use the following MTX Sidecar configuration to achieve this:

::: code-group

```json [.cdsrc.json]
{
    "requires": {
        "multitenancy": true,
        "extensibility": false, // [!code focus]
        "toggles": false // [!code focus]
    },
    "build": {
        ...
    }
}
```

:::

In this case, the application can use its static local model without requesting the MTX sidecar for the model. This results in a significant performance gain because CSN and EDMX metadata are loaded from the JAR instead of the MTX Sidecar. To make the Java application aware of this setup as well, set the following properties:

::: code-group

```yaml [application.yaml]
cds:
  model:
    provider:
      extensibility: false # [!code focus]
      toggles: false # [!code focus]

```

:::
::: tip Enable only the features that you need
You can also selectively use these properties to enable only extensibility or feature toggles, thus decreasing the dimensions when looking up dynamic models.

:::

<div id="subscriptionmanager" />


<br/> <br/> <br/> <br/>

# Appendix

## About SaaS Applications

Software-as-a-Service (SaaS) solutions are deployed once by a SaaS provider, and then used by multiple SaaS customers subscribing to the software.

SaaS applications need to register with the [_SAP BTP SaaS Provisioning service_](https://discovery-center.cloud.sap/serviceCatalog/saas-provisioning-service) to handle `subscribe` and `unsubscribe` events. In contrast to [single-tenant deployments](../deploy/to-cf), databases or other _tenant-specific_ resources aren't created and bootstrapped upon deployment, but upon subscription per tenant.

CAP includes the **MTX services**, which provide out-of-the-box handlers for `subscribe`/`unsubscribe` events, for example to manage SAP HANA database containers.
<!-- , as well as automated updates of subscribed tenants. (Not sure what this means here) -->

If everything is set up, the following graphic shows what's happening when a user subscribes to a SaaS application:

![The graphic is explained in the following text.](assets/saas-overview.drawio.svg){style="margin: 30px auto"}

1. The SaaS Provisioning Service sends a `subscribe` event to the CAP application.
2. The CAP application delegates the request to the MTX services.
3. The MTX services use Service Manager to create the database tenant.
4. The CAP Application connects to this tenant at runtime using Service Manager.

## About Sidecar Setups

The SaaS operations `subscribe` and `upgrade` tend to be resource-intensive. Therefore, it's recommended to offload these tasks onto a separate microservice, which you can scale independently of your main app servers.

Java-based projects even require such a sidecar, as the MTX services are implemented in Node.js.

In these MTX sidecar setups, a subproject is added in _./mtx/sidecar_, which serves the MTX Services as depicted in the illustration below.

![The main app serves the CAP services and the database. The sidecar serves the Deployment service and the Model Provider service. The Deployment service receives upgrade and subscribe request and sends deploy requests to the database of the main app. The Deployment service and the CAP services get the model from the Model Provider service to keep all layers in sync.](./assets/mtx-sidecar.drawio.svg)

The main task for the MTX sidecar is to serve `subscribe` and `upgrade` requests.

The CAP services runtime requests models from the sidecar only when you apply tenant-specific extensions. For Node.js projects, you have the option to run the MTX services embedded in the main app, instead of in a sidecar.
