+
+
+
+
+
diff --git a/.vitepress/theme/components/indexFilter.ts b/.vitepress/theme/components/indexFilter.ts
index 2a5d4a206..fd2328c21 100644
--- a/.vitepress/theme/components/indexFilter.ts
+++ b/.vitepress/theme/components/indexFilter.ts
@@ -15,12 +15,13 @@ export default (pages:ContentDataCustom[], basePath:string):ContentDataCustom[]
return pages
.map(p => {
- p.url = p.url?.replaceAll('@external/', '')?.replace(/\/index$/, '/') || ''
- p.url = join(base, p.url)
- return p
+ const res = { ...p } // do not mutate original data
+ res.url = res.url?.replaceAll('@external/', '')?.replace(/\/index$/, '/') || ''
+ res.url = join(base, res.url)
+ return res
})
.filter(p => {
- const item = items.find(item => item.link && p.url.endsWith(item.link))
+ const item = items.find(item => item.link && p.url.endsWith(item.link.replace(/#.*/, '')))
if (item) p.title = item.text
return !!item
})
diff --git a/.vitepress/theme/styles.scss b/.vitepress/theme/styles.scss
index 2a9fa797f..c5c19b4f3 100644
--- a/.vitepress/theme/styles.scss
+++ b/.vitepress/theme/styles.scss
@@ -370,7 +370,9 @@ main {
.constructor::before { content: 'Constructor: '; color: #999 }
.annotation::before { content: 'Annotation: '; color: #999 }
+ .interface::before { content: 'Interface: '; color: #999 }
.property::before { content: 'Property: '; color: #999 }
+ .type::before { content: 'Type: '; color: #999 }
.method::before { content: 'Method: '; color: #999 }
.event::before { content: 'Event: '; color: #999 }
.class::before { content: 'Class: '; color: #999 }
diff --git a/about/index.md b/about/index.md
index dbf9c1fc4..64c011605 100644
--- a/about/index.md
+++ b/about/index.md
@@ -30,7 +30,7 @@ Someone once said:
### Jumpstarting Projects
-To get started with CAP, there's only a [minimalistic initial setup](../get-started/index.md) required. Starting a project is a matter of seconds. No tedious long lasting platform onboarding ceremonies are required; instead you can:
+To get started with CAP, there's only a [minimalistic initial setup](../get-started/index.md) required. Starting a project is a matter of seconds. No tedious long lasting platform onboarding ceremonies are required; instead you can (and should):
- Start new CAP projects within seconds.
- Create functional apps with full-fledged servers within minutes.
@@ -249,7 +249,7 @@ SaaS customers, verticalization partners, or your teams can...
All of these tasks are done in [the same way as you do in your own projects](best-practices.md#intrinsic-extensibility):
- Using the same techniques of CDS Aspects and Event Handlers
-- Including adaption ad extensions of reuse types/models
+- Including adaption and extensions of reuse types/models
- Including extensions to framework-provided services
And all of that is available out of the box, that is, without you having to create extension points. You would want to restrict who can extend what, though.
diff --git a/advanced/publishing-apis/openapi.md b/advanced/publishing-apis/openapi.md
index a68f1dbda..e29e91f31 100644
--- a/advanced/publishing-apis/openapi.md
+++ b/advanced/publishing-apis/openapi.md
@@ -87,7 +87,7 @@ See [Frequently Asked Questions](#faq) for examples on how to use these annotati
| `Computed` | Property | omit from Create and Update structures |
| `DefaultNamespace` | Schema | path templates for actions and functions without namespace prefix |
| `Description` | Action, ActionImport, Function, FunctionImport | `summary` of Operation Object |
-| `Description` | EntitySet, Singleton | `title` of Tag Object |
+| `Description` | EntitySet, Singleton | `description` of Tag Object |
| `Description` | EntityType | `title` of Request Body Object |
| `Description` | ComplexType, EntityType, EnumerationType, Parameter, Property, TypeDefinition | `title` of Schema Object |
| `Description` | Schema, EntityContainer | `info.title` |
@@ -183,6 +183,13 @@ annotate MyService with @(
[See it in context.](https://github.com/chgeo/cds-swagger-ui-express/blob/e5794c55b53dd3e43ebe8ffcfff69341b6eac9c7/tests/app/services.cds#L23-L34){.learn-more}
+## [Common](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/Common.md)
+
+| Term | Annotation Target | OpenAPI field |
+|--------------------|------------------------------|--------------------------------------------------------------------|
+| `Label` | EntitySet, Singleton | `name` of Tag Object and entry in `tags` array of Operation Object |
+
+
## OpenAPI
| Term | Annotation Target | OpenAPI field |
diff --git a/cds/annotations.md b/cds/annotations.md
index 88e837ee9..25acce256 100644
--- a/cds/annotations.md
+++ b/cds/annotations.md
@@ -102,4 +102,4 @@ Intrinsically supported OData Annotations:
| `@Core.IsMediaType` | see [Media Data](../guides/providing-services#serving-media-data) |
| `@Core.IsUrl` | see [Media Data](../guides/providing-services#serving-media-data) |
| `@Capabilities...` | see [Fiori](../advanced/fiori) |
-| `@Common.FieldControl` | see [Input Validation](../guides/providing-services#input-validation)) |
+| `@Common.FieldControl` | see [Input Validation](../guides/providing-services#common-fieldcontrol) |
diff --git a/cds/aspects.md b/cds/aspects.md
index fd8576ccf..870010e90 100644
--- a/cds/aspects.md
+++ b/cds/aspects.md
@@ -8,7 +8,7 @@ status: released
The technique of [*Aspects*](cdl#aspects) provides a very powerful means to organize your models in a way that keeps your core domain models concise and comprehensible by factoring out secondary concerns into separate files, defining and reusing common aspects, as well as adapting reused definitions to specific needs.
-**See also:** Respective section in [*Five reasons to use CAP*](https://qmacro.org/blog/posts/2024/11/07/five-reasons-to-use-cap/) , and [*Separating concerns and focusing on important stuff*](https://qmacro.org/blog/posts/2024/11/04/separating-concerns-and-focusing-on-the-important-stuff/) blogs by DJ Adams. {.learn-more}
+**See also:** Respective section in [*Five reasons to use CAP*](https://qmacro.org/blog/posts/2024/11/07/five-reasons-to-use-cap/) , and [*Separating concerns and focusing on important stuff*](https://qmacro.org/blog/posts/2024/11/04/separating-concerns-and-focusing-on-the-important-stuff/) blog posts by DJ Adams. {.learn-more}
@@ -145,11 +145,9 @@ annotate CatalogService.Books with @UI:{
## Common Reuse Aspects
-
Quite frequently, you want some common aspects to be factored out and shared by and applied to multiple entities. For example, lets assume we'd want to factor out the common aspects of a standardized primary key, managed data, change tracking, extensibility, and temporal data...
-
### _Max Base Class_ Approach {.avoid}
The classic way to do so, for example in class-based inheritance systems like Java, is to have a central team defining single base classes like `Object` for that, and either add all the aspects in question to that single base class, or have a base class hierarchy, like that:
@@ -343,7 +341,7 @@ extend managed with {
### Adding Reuse Aspects {.best-practice}
-And as the `:` notation to *inherit* an aspect is essentially just [syntactical sugar](cdl#includes) to extending a given definition with a [*named* aspect](cdl#named-aspects), you can also adapt a reused definition to *inherit* from a common reuse aspect from 'the outside' like so:
+And as the `:` notation to *inherit* an aspect is essentially just [syntactical sugar](cdl#includes) for extending a given definition with a [*named* aspect](cdl#named-aspects), you can also adapt a reused definition to *inherit* from a common reuse aspect from 'the outside' like so:
```cds
using { SomeEntity } from 'some-reuse-package';
@@ -398,7 +396,7 @@ Verticalization means to adapt a given application for different regions or indu
## Inheritance Hierarchies
-Sometimes you'd be tempted to create deeply nested inheritance hierarchies as you might be used to do in Java. For example, lets assume we're tempted to model something like that:
+Sometimes you'd be tempted to create deeply nested inheritance hierarchies as you might be used to do in Java. For example, let's assume we're tempted to model something like that:
```cds
abstract entity Grantees { // equivalent to aspect
diff --git a/cds/cdl.md b/cds/cdl.md
index a067bd6e9..7eb56429c 100644
--- a/cds/cdl.md
+++ b/cds/cdl.md
@@ -595,7 +595,7 @@ No restrictions apply for reading a calculated element on-write.
#### Association-like calculated elements {#association-like-calculated-elements}
-A calculated element can also define a filtered association or composition, like in this example:
+A calculated element can also define a filtered association/composition using infix filters:
```cds
entity Employees {
@@ -607,7 +607,7 @@ entity Employees {
For such a calculated element, no explicit type can be specified.
Only a single association or composition can occur in the expression, and a filter must be specified.
-The effect essentially is like [publishing an association with a filter](#publish-associations-with-filter).
+The effect essentially is like [publishing an association with an infix filter](#publish-associations-with-filter).
### Default Values
@@ -1062,7 +1062,7 @@ entity P_Employees as projection on Employees {
The effective signature of the projection contains an association `addresses` with the same
properties as association `addresses` of entity `Employees`.
-#### Publish Associations with Filter {#publish-associations-with-filter}
+#### Publish Associations with Infix Filter {#publish-associations-with-filter}
When publishing an unmanaged association in a view or projection, you can add a filter condition.
The ON condition of the resulting association is the ON condition of the original
@@ -1388,9 +1388,10 @@ Each path in the expression is checked:
* A parameter `par` can be accessed via `:par`, just like parameters of a parametrized entity in queries.
* For an annotation assigned to a bound action or function, elements of the respective entity
can be accessed via `$self`.
-* The draft specific element `IsActiveEntity` can be referred to with the magic variable `$draft.IsActiveEntity`.
- During draft augmentation `$draft.IsActiveEntity` is rewritten to `$self.IsActiveEntity` for all draft enabled
- entities (root and sub nodes but not for named types or entity parameters).
+* The draft-specific elements `IsActiveEntity`, `HasActiveEntity`, and `HasDraftEntity` can be referred to with
+ respective magic variables `$draft.IsActiveEntity`, `$draft.HasActiveEntity`, and `$draft.HasDraftEntity`.
+ During draft augmentation, `$draft.<...>` is rewritten to `$self.<...>` for all draft enabled
+ entities (root and sub nodes, but not for named types or entity parameters).
* If a path can't be resolved successfully, compilation fails with an error.
In contrast to `@aReference: foo.bar`, a single reference written as expression `@aRefExpr: ( foo.bar )`
@@ -1779,7 +1780,8 @@ extend SomeView with columns {
}
```
-Enhancing nested structs isn't supported.
+Enhancing nested structs isn't supported. Furthermore, the table alias of the view's data source
+is not accessible in such an extend.
You can use the common [`annotate` directive](#annotate) to just add/override annotations of a view's elements.
diff --git a/cds/common.md b/cds/common.md
index 4778e5253..cc86c6913 100644
--- a/cds/common.md
+++ b/cds/common.md
@@ -262,7 +262,7 @@ entity sap.common.Timezones : CodeList {
}
```
-[Learn more about time zones in Javascript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) {.learn-more}
+[Learn more about time zones in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) {.learn-more}
[Learn more about time zones in Java](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/ZoneId.html) {.learn-more}
diff --git a/cds/cqn.md b/cds/cqn.md
index d3a417653..262458085 100644
--- a/cds/cqn.md
+++ b/cds/cqn.md
@@ -4,316 +4,543 @@ shorty: Query Notation
synopsis: >
Specification of the Core Query Notation (CQN) format that is used to capture queries as plain JavaScript objects.
status: released
-uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/855e00bd559742a3b8276fbed4af1008.html
---
# Query Notation (CQN)
-[expr]: cxn#expressions
-[xpr]: cxn#operators
-[ref]: cxn#references
-[val]: cxn#literal-values
-[_xpr]: cxn#operators
+[[toc]]
-CQN is a canonical plain object representation of CDS queries. Such query objects can be obtained by parsing [CQL](./cql), by using the [query builder APIs](../node.js/cds-ql), or by simply constructing respective objects directly in your code.
-#### Examples
+## Introduction
+
+CQN is a canonical plain object representation of CDS queries. Such query objects can be obtained by parsing [CQL](./cql), by using the [query builder APIs](../node.js/cds-ql), or by simply constructing respective objects directly in your code.
-The following three snippets all construct the same query object:
+For example, the following three snippets all construct the same query object:
```js
-// Parsing CQL
-let query = cds.parse.cql (`SELECT from Foo`)
+// Parsing CQL tagged template strings
+let query = cds.ql `SELECT from Foo`
```
```js
// Query building
-let query = SELECT.from('Foo')
+let query = SELECT.from (ref`Foo`)
```
```js
-// Constructing CQN objects in your code
+// Constructing plain CQN objects
let query = {SELECT:{from:[{ref:['Foo']}]}}
```
-That object can be [executed with `cds.run`](../node.js/core-services#srv-run-query):
+Such queries can be [executed with `cds.run`](../node.js/core-services#srv-run-query):
```js
-cds.run (query)
+let results = await cds.run (query)
```
-::: warning _β Warning_
-Because of SQL injection, it's strongly discouraged to use `cds.parse.cql` in your request handlers.
-:::
-
-
-#### Content
-
-[[toc]]
+Following is a detailed specification of the CQN as [TypeScript declarations](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html), including all query types and their properties,
+as well as the fundamental expression types. Find the [full CQN type definitions in the appendix below](#full-cqn-d-ts-file).
## SELECT
-[SELECT]: #select
-
-A fully equipped `SELECT` query is represented as an object following this template (all properties except `from` are optional):
-```js
-SELECT = {SELECT:{
- distinct: true,
- from: source | join,
- mixin: { ...element },
- columns: projection,
- excluding: [ ...string ],
- where: _xpr,
- groupBy: [ ...expr ],
- having: _xpr,
- orderBy: [ ...ordering_term ],
- limit: { rows:expr, offset:expr },
- forUpdate: { wait: number },
- forShareLock: { wait: number },
- search: _xpr,
- count: Boolean
+Following is the TypeScript declaration of `SELECT` query objects:
+
+```tsx
+class SELECT { SELECT: {
+ distinct? : true
+ count? : true
+ one? : true
+ from : source
+ columns? : column[]
+ where? : xo[]
+ having? : xo[]
+ groupBy? : expr[]
+ orderBy? : order[]
+ limit? : { rows: val, offset: val }
}}
```
+> Using:
+> [`source`](#source),
+> [`colum`](#column),
+> [`xo`](#xo),
+> [`expr`](#expr),
+> [`order`](#order),
+> [`val`](#val)
-| Property | Description |
-|-------------|---------------------------------------------------------------------------|
-| `from` | a primary [source] or [joined sources][joins] |
-| `mixin` | a dictionary of several [CSN element definitions](./csn#structured-types) |
-| `columns` | an array of [column expressions](#columns) |
-| `excluding` | an array of names |
-| `where` | a [predicate expression][_xpr] |
-| `groupBy` | an array of [expressions][expr] |
-| `having` | a [predicate expression][_xpr] |
-| `orderBy` | an array of [ordering terms](#ordering-terms) |
-| `limit` | a dictionary of two [expressions][expr]: rows and offset |
-| `search` | a [predicate expression][_xpr] |
-| `count` | a Boolean |
+CQL SELECT queries enhance SQL's SELECT statements with these noteworthy additions:
+- The `from` clause supports [`{ref}`](#ref) paths with *[infix filters](#infix)*.
+- The `columns` clause supports deeply *[nested projections](#expand)*.
+- The `count` property requests the total count, similar to OData's `$count`.
+- The `one` property causes a single row object to be read instead of an array.
+
+Also `SELECT` statements with `from` as the only mandatory property are allowed,
+which is equivalent to SQL's `SELECT * from ...`.
-```js
-source = ( ref | SELECT ) + { as:string }
-join = { join:string, args:[...source], on:_xpr }
-projection = [ ...column_expr ]
-column_expr = expr + { as:string, cast:def, (expand|inline):projection }
-ordering_term = expr + { sort: 'asc'|'desc', nulls: 'first'|'last' }
-```
-**Sources** are [references][ref] or [subqueries][SELECT] with an optional: { #sources}
-[source]: #sources
-[sources]: #sources
+### `.from`
+###### source
-* `as` β a string specifying a chosen source alias
+Property `from` specifies the source of the query, which can be a table, a view, or a subquery.
+It is specified with type `source` as follows:
-**Joins** combine two [sources] with these properties: { #joins}
+```tsx
+class SELECT { SELECT: { //...
+ from : source // [!code focus]
+}}
+```
+```tsx
+type source = ref &as | SELECT | {
+ join : 'inner' | 'left' | 'right'
+ args : [ source, source ]
+ on? : expr
+}
+```
+> Using:
+> [`ref`](#ref),
+> [`as`](#as),
+> [`expr`](#expr)
+>
+> Used in:
+> [`SELECT`](#select)
-[joins]: #joins
-[join]: #joins
-* `join` is one of `'left'`, `'right'`, `'full'`, `'inner'`, or `'cross'`
-* `args` is an array of two [sources] or [joins]
-* `on` is a [predicate expression][_xpr] capturing the JOIN condition
-**Column Expressions** are a plain string `'*'`, or [expressions][expr] with these optional additional properties: { #columns}
+### `.columns`
-* `as` is a string with the chosen name in the result set
-* `cast` is a [CSN type definition](./csn#type-definitions)
-* `inline` \| `expand` are nested [projections][SELECT]
+###### column
+###### as
+###### cast
+###### infix
+###### expand
-**Ordering Terms** are [expressions][expr], usually [references][ref], with one or none of... { #ordering-terms}
+Property `columns` specifies the columns to be selected, projected, or aggregated, and is specified as an array of `column`s:
-* `sort` = 'asc' \| 'desc'
-* `nulls` = 'first' \| 'last'
+```tsx
+class SELECT { SELECT: { //...
+ columns : column[] // [!code focus]
+}}
+```
+```tsx
+type column = '*' | expr &as &cast | ref &as &(
+ { expand?: column[] } |
+ { inline?: column[] }
+) &infix
+```
+```tsx
+interface as { as?: name }
+interface cast { cast?: {type:name} }
+interface infix {
+ orderBy? : order[]
+ where? : expr
+ limit? : { rows: val, offset: val }
+}
+```
+> Using:
+> [`expr`](#expr),
+> [`name`](#name),
+> [`ref`](#ref),
+>
+> Used in:
+> [`SELECT`](#select)
+
+
+### `.where`
+### `.having`
+### `.search`
+
+Properties `where`, and `having`, specify the filter predicates to be applied to the rows selected, or grouped, respectively. Property `search` is of same kind and is used for full-text search.
+
+```tsx
+class SELECT { SELECT: {
+ where : xo[] // [!code focus]
+ having : xo[] // [!code focus]
+ search : xo[] // [!code focus]
+}}
+```
-### Example
-For example, the following query in CQL:
+### `.orderBy`
+###### order
-```sql
-SELECT from samples.bookshop.Books {
- title, author.name as author,
- 1 as one,
- x+2 as two : Integer,
-} excluding {
- dummy
+```tsx
+class SELECT { SELECT: { //...
+ orderBy : order[] // [!code focus]
+}}
+```
+```tsx
+type order = expr & {
+ sort : 'asc' | 'desc'
+ nulls : 'first' | 'last'
}
-WHERE ID=111
-GROUP BY x.y
-HAVING x.y<9
-ORDER BY title asc
-LIMIT 11 OFFSET 22
```
+> Using:
+> [`expr`](#expr)
+>
+> Used in:
+> [`SELECT`](#select)
+>
+
-is represented in CQN as:
+## INSERT
+## UPSERT
-```js
-CQN = {SELECT:{
- from: {ref:["samples.bookshop.Books"]},
- columns: [
- {ref:["title"]},
- {ref:["author","name"], as: "author"},
- {val:1, as: "one"},
- {xpr:[{ref:['x']}, '+', {val:2}], as: "two",
- cast: {type:"cds.Integer"}
- }
- ],
- excluding: [
- "dummy"
- ],
- where: [{ref:["ID"]}, "=", {val: 111}],
- groupBy: [{ref:["x","y"]}],
- having: [{ref:["x","y"]}, "<", {val: 9}],
- orderBy: [{ref:["title"], sort:'asc' }],
- limit: {rows:{val:11}, offset:{val:22}}
+CQN representations for `INSERT` and `UPSERT` are essentially identical:
+
+```tsx
+class INSERT { INSERT: UPSERT['UPSERT'] }
+class UPSERT { UPSERT: {
+ into : ref
+ entries? : data[]
+ columns? : string[]
+ values? : scalar[]
+ rows? : scalar[][]
+ from? : SELECT
}}
```
+```tsx
+interface data { [elm:string]: scalar | data | data[] }
+```
-
+> Using:
+> [`ref`](#ref),
+> [`expr`](#expr)
+> [`scalar`](#scalar),
+> [`SELECT`](#select)
+>
+> See also:
+> [`UPDATE.data`](#data),
-
+Data to be inserted can be specified in one of the following ways:
-## UPSERT
+* Using [`entries`](#entries) as an array of records with name-value pairs.
+* Using [`values`](#values) as in SQL's _values_ clauses.
+* Using [`rows`](#rows) as an array of one or more `values`.
-```js
-UPSERT = {UPSERT:{
- into: ref + { as:string },
- entries: [ ...{ ...column:any } ],
- as: SELECT
-}}
-```
+The latter two options require a `columns` property to specify names of columns
+to be filled with the values in the same order.
-## INSERT
+
+### `.entries`
+
+Allows input data to be specified as records with name-value pairs,
+including _deep_ inserts.
```js
-INSERT = {INSERT:{
- into: ref + { as:string },
- columns: [ ...string ],
- values: [ ...any ],
- rows: [ ...[ ...any ] ],
- entries: [ ...{ ...column:any } ],
- as: SELECT
-}}
+let q = {INSERT:{ into: { ref: ['Books'] }, entries: [
+ { ID:201, title:'Wuthering Heights' },
+ { ID:271, title:'Catweazle' }
+]}}
```
+```js
+let q = {INSERT:{ into: { ref: ['Authors'] }, entries: [
+ { ID:150, name:'Edgar Allen Poe', books: [
+ { ID:251, title:'The Raven' },
+ { ID:252, title:'Eleonora' }
+ ]}
+]}}
+```
+[See definition in `INSERT` summary](#insert) {.learn-more}
+
+
-Either and only one of the properties `values` or `rows` or `entries` is expected to be specified. Each of which is expected to have one or more entries:
+### `.values`
-* `values` is an array of values, which positionally match to specified `columns`.
-* `rows` is an array of one or more `values`.
-* `entries` is an array of records with name-value pairs.
+{#scalar}
-Examples:
+Allows input data to be specified as an single array of values, as in SQL.
```js
-CQN = {INSERT:{
- into: { ref: ['Books'] },
+let q = {INSERT:{ into: { ref: ['Books'] },
columns: [ 'ID', 'title', 'author_id', 'stock' ],
values: [ 201, 'Wuthering Heights', 101, 12 ]
}}
```
+[See definition in `INSERT` summary](#insert) {.learn-more}
+
+
+### `.rows`
+
+Allows input data for multiple rows to be specified as arrays of values.
+
```js
-CQN = {INSERT:{
- into: { ref: ['Books'] },
- columns: [ 'ID', 'title', 'author_id', 'stock' ],
+let q = {INSERT:{ into: { ref: ['Books'] },
+ columns: [
+ 'ID', 'title', 'author_id', 'stock'
+ ],
rows: [
[ 201, 'Wuthering Heights', 101, 12 ],
- [ 251, 'The Raven', 150, 333 ],
[ 252, 'Eleonora', 150, 234 ]
]
}}
```
-```js
-CQN = {INSERT:{
- into: { ref: ['Books'], as: 'NewBooks' },
- entries: [
- { ID:201, title:'Wuthering Heights', author_id:101, stock:12 },
- { ID:251, title:'The Raven', author_id:150, stock:333 },
- { ID:271, title:'Catweazle', author_id:170, stock:222 }
- ]
+[See definition in `INSERT` summary](#insert) {.learn-more}
+
+
+
+## UPDATE
+
+```tsx
+class UPDATE { UPDATE: {
+ entity : ref
+ where? : expr
+ data : data
+ with : changes
}}
```
+> Using:
+> [`ref`](#ref),
+> [`expr`](#expr),
+> [`data`](#data),
+> [`changes`](#changes)
-The last one also allows to express so-called 'deep inserts'. Let's assume we want to store an author with two books:
-```js
-CQN = {INSERT:{ into: { ref: ['Authors'] }, entries: [
- { ID:150, name:'Edgar Allen Poe', books:[
- { ID:251, title:'The Raven' },
- { ID:252, title:'Eleonora' }
- ] }
-]}}
-```
+### `.data`
-Instead of inserting new entries for books we might want to just add relationships to already existing books, in that case just specify one or more primary key values of the target instance.
+Data to be updated can be specified in property `data` as records with name-value pairs, same as in [`INSERT.entries`](#entries).
-```js
-CQN = {INSERT:{ into: { ref: ['Authors'] }, entries: [
- { ID:150, name:'Edgar Allen Poe', books:[
- 251, 252,
- ] }
-]}}
+```tsx
+interface data { [element:name]: scalar | data | data[] }
```
+> Using:
+> [`name`](#name),
+> [`scalar`](#scalar)
-## UPDATE
+### `.with`
+###### changes
-```js
-UPDATE = {UPDATE:{
- entity: ref + { as:string },
- data: { ...column:any },
- where: _xpr
-}}
+Property `with` specifies the changes to be applied to the data, very similar to property [`data`](#data) with the difference to also allow [expressions](#expressions) as values.
+
+```tsx
+interface changes { [element:name]: scalar | expr | changes | changes[] }
```
+> Using:
+> [`name`](#name),
+> [`expr`](#expr),
+> [`scalar`](#scalar)
+
+
## DELETE
```js
-DELETE = {DELETE:{
- from: ref + { as:string },
- where: _xpr
+class DELETE { DELETE: {
+ from : ref
+ where? : expr
}}
```
+> Using:
+> [`ref`](#ref),
+> [`expr`](#expr)
+
+
+## Expressions
+###### expr
+###### ref
+###### val
+###### xpr
+###### list
+###### func
+###### param
+###### xo
+###### name
+###### scalar
+
+Expressions can be entity or element references, query parameters,
+literal values, lists of all the former, function calls, sub selects,
+or compound expressions.
+
+```tsx
+type expr = ref | val | xpr | list | func | param | SELECT
+```
+```tsx
+type ref = { ref: ( name | { id:name &infix })[] }
+type val = { val: scalar }
+type xpr = { xpr: xo[] }
+type list = { list: expr[] }
+type func = { func: string, args: expr[] }
+type param = { ref: [ '?' | number | string ], param: true }
+```
-
-## CREATE
-
-```js
-CREATE = {CREATE:{
- entity: entity | string,
- as: SELECT
-}}
+```tsx
+type xo = expr | keyword | operator
+type operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>='
+type keyword = 'in' | 'like' | 'and' | 'or' | 'not'
+type scalar = number | string | boolean | null
+type name = string
```
+>[!note]
+> CQN by intent does not _understand_ expressions and therefore
+> keywords and operators are just represented as plain strings in flat
+> `xo` sequences. This allows us to translate to and from any other query languages,
+> including support for native SQL features.
+
+
+
+
+
+## Full `cqn.d.ts` File
+
+::: code-group
+
+```tsx [cqn.d.ts]
+/**
+ * `INSERT` and `UPSERT` queries are represented by the same internal
+ * structures. The `UPSERT` keyword is used to indicate that the
+ * statement should be updated if the targeted data exists.
+ * The `into` property specifies the target entity.
+ *
+ * The data to be inserted or updated can be specified in different ways:
+ *
+ * - in the `entries` property as deeply nested records.
+ * - in the `columns` and `values` properties as in SQL.
+ * - in the `columns` and `rows` properties, with `rows` being array of `values`.
+ * - in the `from` property with a `SELECT` query to provide the data to be inserted.
+ *
+ * The latter is the equivalent of SQL's `INSERT INTO ... SELECT ...` statements.
+ */
+export class INSERT { INSERT: UPSERT['UPSERT'] }
+export class UPSERT { UPSERT: {
+ into : ref
+ entries? : data[]
+ columns? : string[]
+ values? : scalar[]
+ rows? : scalar[][]
+ from? : SELECT
+}}
-## DROP
-```js
-DROP = {DROP:{
- table: ref,
- view: ref,
- entity: ref
+/**
+ * `UPDATE` queries are used to capture modifications to existing data.
+ * They support a `where` clause to specify the rows to be updated,
+ * and a `with` clause to specify the new values. Alternatively, the
+ * `data` property can be used to specify updates with plain data only.
+ */
+export class UPDATE { UPDATE: {
+ entity : ref
+ where? : expr
+ data : data
+ with : changes
}}
-```
-Examples:
-```js
-CQN = {DROP:{
- table: { ref: ['Books'] }
-}}
-```
-```js
-CQN = {DROP:{
- view: { ref: ['Books'] }
+/**
+ * `DELETE` queries are used to remove data from a target datasource.
+ * They support a `where` clause to specify the rows to be deleted.
+ */
+export class DELETE { DELETE: {
+ from : ref
+ where? : expr
}}
-```
-```js
-CQN = {DROP:{
- entity: { ref: ['Books'] }
+
+/**
+ * `SELECT` queries are used to retrieve data from a target datasource,
+ * and very much resemble SQL's `SELECT` statements, with these noteworthy
+ * additions:
+ *
+ * - The `from` clause supports `{ref}` paths with infix filters.
+ * - The `columns` clause supports deeply nested projections.
+ * - The `count` property requests the total count, similar to OData's `$count`.
+ * - The `one` property indicates that only a single record object shall be
+ * returned instead of an array.
+ *
+ * Also, CDS, and hence CQN, supports minimalistic `SELECT` statements with a `from`
+ * as the only mandatory property, which is equivalent to SQL's `SELECT * from ...`.
+ */
+export class SELECT { SELECT: {
+ distinct? : true
+ count? : true
+ one? : true
+ from : source
+ columns? : column[]
+ where? : xo[]
+ having? : xo[]
+ groupBy? : expr[]
+ orderBy? : order[]
+ limit? : { rows: val, offset: val }
}}
+
+type source = OneOf< ref &as | SELECT | {
+ join : 'inner' | 'left' | 'right'
+ args : [ source, source ]
+ on? : expr
+}>
+
+type column = OneOf< '*' | expr &as &cast | ref &as & OneOf<(
+ { expand?: column[] } |
+ { inline?: column[] }
+)> &infix >
+
+type order = expr & {
+ sort : 'asc' | 'desc'
+ nulls : 'first' | 'last'
+}
+
+
+interface changes { [elm:string]: OneOf< scalar | expr | changes | changes[] >}
+interface data { [elm:string]: OneOf< scalar | data | data[] >}
+interface as { as?: name }
+interface cast { cast?: {type:name} }
+
+interface infix {
+ orderBy? : order[]
+ where? : expr
+ limit? : { rows: val, offset: val }
+}
+
+
+/**
+ * Expressions can be entity or element references, query parameters,
+ * literal values, lists of all the former, function calls, sub selects,
+ * or compound expressions.
+ */
+export type expr = OneOf< ref | val | xpr | list | func | param | SELECT >
+export type ref = { ref: OneOf< name | { id:name &infix } >[] }
+export type val = { val: scalar }
+export type xpr = { xpr: xo[] }
+export type list = { list: expr[] }
+export type func = { func: string, args: expr[] }
+export type param = { ref: [ '?' | number | string ], param: true }
+
+/**
+ * This is used in `{xpr}` objects as well as in `SELECT.where` clauses to
+ * represent compound expressions as flat `xo` sequences.
+ * Note that CQN by intent does not _understand_ expressions and therefore
+ * keywords and operators are just represented as plain strings.
+ * This allows us to translate to and from any other query languages,
+ * including support for native SQL features.
+ */
+type xo = OneOf< expr | keyword | operator >
+type operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>='
+type keyword = 'in' | 'like' | 'and' | 'or' | 'not'
+type scalar = number | string | boolean | null
+type name = string
+
+
+
+// ---------------------------------------------------------------------------
+// maybe coming later...
+
+declare class CREATE { CREATE: {} }
+declare class DROP { DROP: {} }
+
+
+// ---------------------------------------------------------------------------
+// internal helpers...
+
+type OneOf = Partial<(U extends any ? (k:U) => void : never) extends (k: infer I) => void ? I : never>
```
+:::
\ No newline at end of file
diff --git a/get-started/index.md b/get-started/index.md
index cd68aba8b..0105c111e 100644
--- a/get-started/index.md
+++ b/get-started/index.md
@@ -11,7 +11,7 @@ Welcome to CAP, and to *capire*, its one-stop documentation.
[**capire** [caΒ·pì·re] β Italian for _"understand"_](https://translate.google.com/details?sl=it&tl=en&text=capire){.learn-more .dict}
-
@@ -24,7 +24,7 @@ Follow the steps after this for a minimalistic local setup. Alternatively, you c
-#### Prerequisites
+### Prerequisites
- [Node.js](https://nodejs.org) β required for installing the `cds` command line interface.
- [SQLite](https://sqlite.org) β included in macOS and Linux β [install it](https://sqlite.org/download.html) on Windows.
@@ -32,7 +32,7 @@ Follow the steps after this for a minimalistic local setup. Alternatively, you c
- **A Text Editor**{style="font-weight: 500"} β we recommend [VS Code](https://code.visualstudio.com) with [CDS plugin](../tools/cds-editors#vscode).
-#### Installation
+### Installation
- With the prerequisites met, install the [`cds` toolkit](../tools/cds-cli) *globally*:
@@ -79,7 +79,7 @@ Follow the steps after this for a minimalistic local setup. Alternatively, you c
-#### Optional
+### Optional
- [Java](https://sapmachine.io) & [Maven](https://maven.apache.org/download.cgi) β if you're going for Java development β [see instructions](../java/getting-started#local).
- [git](https://git-scm.com) β if you go for more than just some quick trials.
@@ -146,19 +146,32 @@ We recommend sticking to CAP's way of [Convention over Configuration](https://en
-## Next Steps...
+## Learning More
+{#next-steps}
After the [initial setup](#setup), we recommend continuing as follows while you grow as you go...:
-| # | Guide | Description |
-|---|---------------------------------------------------------------------------------------|--------------------------------------------------------|
-| 1 | [Introduction β What is CAP?](../about/) | Learn about key benefits and value propositions. |
-| 2 | [Bookshop by capire](in-a-nutshell) | Build your first CAP application within 4-44 minutes. |
-| 3 | [Best Practices](../about/best-practices) | Key concepts & rationales to understand β *must read*. |
-| 4 | [Anti Patterns](../about/bad-practices) | Misconceptions & bad practices to avoid β *must read*. |
-| 5 | [Learn More](learning-sources) | Find samples, videos, blogs, tutorials, and so on. |
-| 6 | [Cookbook](../guides/) | Walkthroughs for the most common tasks. |
-| 7 | [CDS](../cds/) [Java](../java/) [Node.js](../node.js/) [Tools](../tools/) | The reference docs for these respective areas. |
+| # | Guide | Description |
+|---|-------------------------------------------|--------------------------------------------------------|
+| 1 | [Introduction β What is CAP?](../about/) | Learn about key benefits and value propositions. |
+| 2 | [Bookshop by capire](in-a-nutshell) | Build your first CAP application within 4-44 minutes. |
+| 3 | [Best Practices](../about/best-practices) | Key concepts & rationales to understand β *must read*. |
+| 4 | [Anti Patterns](../about/bad-practices) | Misconceptions & bad practices to avoid β *must read*. |
+| 5 | [Learn More](learning-sources) | Find samples, videos, blogs, tutorials, and so on. |
+
+
+
+## Grow as you go...
+
+After these getting started-level introductions, you would continuously revisit these guides to deepen your understanding and as reference docs:
+
+| # | Guides & References | Description |
+|---:|---------------------------------------------------------------------------------------|------------------------------------------------|
+| 6 | [Cookbook](../guides/) | Walkthroughs for the most common tasks. |
+| 7 | [CDS](../cds/) [Java](../java/) [Node.js](../node.js/) [Tools](../tools/) | The reference docs for these respective areas. |
+| 8 | [Plugins](../plugins/) | Curated list of recommended Calesi plugins. |
+| 9 | [Releases](../releases/) | Your primary source to stay up to date. |
+| 10 | [Resources](../resources/) | About support channels, community, ... |
This also reflects the overall structure of [this documentation](./learning-sources.md#this-documentation).
diff --git a/get-started/troubleshooting.md b/get-started/troubleshooting.md
index 9fceb5a02..12a3efba7 100644
--- a/get-started/troubleshooting.md
+++ b/get-started/troubleshooting.md
@@ -498,6 +498,15 @@ Options in [Saas Provisioning Service upgrade API](../guides/multitenancy/mtxs#e
+#### Deployment fails β _... build plugin for file suffix "hdbmigrationtable" [8210015]_
+{#missingPlugin}
+
+| | Explanation |
+| --- | ---- |
+| _Root Cause_ | Your project configuration is missing some configuration in your _.hdiconfig_ file. |
+| _Solution_ | Use `cds add hana` to add the needed configuration to your project. Or maintain the _hdbmigrationtable_ plugin in your _.hdiconfig_ file manually: `"hdbmigrationtable": { "plugin_name": "com.sap.hana.di.table.migration" }`
+
+
#### Deployment fails β _In USING declarations only main artifacts can be accessed, not sub artifacts of \_
This error occurs if all of the following applies:
+ You [added native SAP HANA objects](../advanced/hana#add-native-objects) to your CAP model.
@@ -593,7 +602,7 @@ mbt build -t gen --mtar mta.tar -e less.mtaext
::: warning
This approach is only recommended
-- For test deployments during _development_. For _production_ deployments, self-contained archives ar preferrable.
+- For test deployments during _development_. For _production_ deployments, self-contained archives are preferrable.
- If all your dependencies are available in _public_ registries like npmjs.org or Maven Central. Dependencies from _corporate_ registries are not resolvable in this mode.
:::
diff --git a/guides/data-privacy/annotations.md b/guides/data-privacy/annotations.md
index 0426ea515..9e82e66df 100644
--- a/guides/data-privacy/annotations.md
+++ b/guides/data-privacy/annotations.md
@@ -2,7 +2,7 @@
status: released
---
-
diff --git a/guides/databases-hana.md b/guides/databases-hana.md
index cf87722e3..bd96c74fa 100644
--- a/guides/databases-hana.md
+++ b/guides/databases-hana.md
@@ -693,10 +693,14 @@ or even unnecessary drop/create of indexes. By switching them off, all this effo
:::
+
For new projects, `cds add hana` automatically adds this configuration.
-Note that the first deployment after this configuration change may take longer, as for
-each entity with associations the respective database object will be touched
+::: warning Initial full table migration
+Be aware, that the first deployment after this **configuration change may take longer**.
+
+For each entity with associations, the respective database object will be touched
(DROP/CREATE for views, full table migration via shadow table and data copy for tables).
This is also the reason why we haven't changed the default so far.
Subsequent deployments will benefit, however.
+:::
diff --git a/guides/databases.md b/guides/databases.md
index 31e1514f7..698000bb1 100644
--- a/guides/databases.md
+++ b/guides/databases.md
@@ -782,13 +782,35 @@ The following rules apply:
a potential name mapping yourself, for example, for structured elements.
- Annotation `@sql.prepend` is only supported for entities translating to tables. It can't be used with views nor with elements.
-- For SAP HANA tables, there's an implicit that is overwritten by an explicitly provided `@sql.prepend`.
+- For SAP HANA tables, there's an implicit `@sql.prepend: 'COLUMN'` that is overwritten by an explicitly provided `@sql.prepend`.
* Both `@sql.prepend` and `@sql.append` are disallowed in SaaS extension projects.
If you use native database clauses in combination with `@cds.persistence.journal`, see [Schema Evolution Support of Native Database Clauses](databases-hana#schema-evolution-native-db-clauses).
+
+#### Creating a Row Table on SAP HANA
+
+By using `@sql.prepend: 'ROW'`, you can create a row table:
+
+```cds
+@sql.prepend: 'ROW'
+entity E {
+ key id: Integer;
+}
+```
+
+Run `cds compile - 2 hdbtable` on the previous sample and this is the result:
+
+```sql [E.hdbtable]
+ROW TABLE E (
+ id INTEGER NOT NULL,
+ PRIMARY KEY(id)
+)
+```
+
+[Learn more about Columnar and Row-Based Data Storage](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-administration-guide/columnar-and-row-based-data-storage){.learn-more}
### Reserved Words
The CDS compiler and CAP runtimes provide smart quoting for reserved words in SQLite and in SAP HANA so that they can still be used in most situations. But in general reserved words cannot be used as identifiers. The list of reserved words varies per database.
diff --git a/guides/deployment/assets/apps-cockpit.png b/guides/deployment/assets/apps-cockpit.png
new file mode 100644
index 000000000..aa8a1e6b8
Binary files /dev/null and b/guides/deployment/assets/apps-cockpit.png differ
diff --git a/guides/deployment/assets/deploy-kyma.drawio.svg b/guides/deployment/assets/deploy-kyma.drawio.svg
new file mode 100644
index 000000000..6861af727
--- /dev/null
+++ b/guides/deployment/assets/deploy-kyma.drawio.svg
@@ -0,0 +1,298 @@
+
\ No newline at end of file
diff --git a/guides/deployment/assets/deploy-overview.drawio.svg b/guides/deployment/assets/deploy-overview.drawio.svg
new file mode 100644
index 000000000..a1c54de7b
--- /dev/null
+++ b/guides/deployment/assets/deploy-overview.drawio.svg
@@ -0,0 +1,98 @@
+
\ No newline at end of file
diff --git a/guides/deployment/assets/deploy-setps.drawio.svg b/guides/deployment/assets/deploy-setps.drawio.svg
new file mode 100644
index 000000000..c17568619
--- /dev/null
+++ b/guides/deployment/assets/deploy-setps.drawio.svg
@@ -0,0 +1,135 @@
+
\ No newline at end of file
diff --git a/guides/deployment/assets/overview.drawio.svg b/guides/deployment/assets/overview.drawio.svg
new file mode 100644
index 000000000..43167d56b
--- /dev/null
+++ b/guides/deployment/assets/overview.drawio.svg
@@ -0,0 +1,337 @@
+
\ No newline at end of file
diff --git a/guides/deployment/cicd.md b/guides/deployment/cicd.md
new file mode 100644
index 000000000..c26ac3a5a
--- /dev/null
+++ b/guides/deployment/cicd.md
@@ -0,0 +1,47 @@
+---
+breadcrumbs:
+ - Cookbook
+ - Deployment
+ - More Options
+label: Deploy with CI/CD
+synopsis: >
+ A comprehensive guide to implementing continuous integration and continuous deployment (CI/CD) for CAP projects using best practices, tools, and services.
+# layout: cookbook
+status: released
+---
+
+# Deploy using CI/CD Pipelines
+{{ $frontmatter.synopsis }}
+
+[[toc]]
+
+
+
+
+
+
+## SAP CI/CD Service
+
+SAP Continuous Integration and Delivery is a service on SAP BTP, which lets you configure and run predefined continuous integration and delivery pipelines. It connects with your Git SCM repository and in its user interface, you can easily monitor the status of your builds and detect errors as soon as possible, which helps you prevent integration problems before completing your development.
+
+SAP Continuous Integration and Delivery has a ready-to-use pipeline for CAP, that is applicable to Node.js, Java and multitarget application (MTA) based projects. It does not require you to host your own Jenkins instance and it provides an easy, UI-guided way to configure your pipelines.
+
+Try the tutorial [Configure and Run a Predefined SAP Continuous Integration and Delivery (CI/CD) Pipeline](https://developers.sap.com/tutorials/btp-app-ci-cd-btp.html) to configure a CI/CD pipeline that builds, tests, and deploys your code changes.
+
+[Learn more about SAP Continuous Integration and Delivery.](https://help.sap.com/viewer/SAP-Cloud-Platform-Continuous-Integration-and-Delivery){.learn-more}
+
+
+## CI/CD Pipelines with SAP Piper
+
+You can set up continuous delivery in your software development project, applicable to both SAP Business Technology Platform (BTP) and SAP on-premise platforms. SAP implements tooling for continuous delivery in project [Piper](https://www.project-piper.io/).
+
+Try the tutorial [Create Automated System Tests for SAP Cloud Application Programming Model Projects](https://developers.sap.com/tutorials/cicd-wdi5-cap.html) to create system tests against a CAP-based sample application and automate your tests through a CI/CD pipeline.
+
+
+[Learn more about project Piper](https://www.project-piper.io/){.learn-more}
+
+## GitHub Actions
+
+GitHub offers continuous integration workflows using [GitHub Actions](https://docs.github.com/en/actions/automating-builds-and-tests/about-continuous-integration). In our [SFlight sample,](https://github.com/SAP-samples/cap-sflight) we use GitHub Actions in two simple workflows to test our samples on [current Node.js and Java versions](https://github.com/SAP-samples/cap-sflight/tree/main/.github/workflows).
+
+We also defined our [own actions](https://github.com/SAP-samples/cap-sflight/tree/main/.github/actions) and use them in a [custom workflow](https://github.com/SAP-samples/cap-sflight/blob/main/.github/workflows/deploy-btp.yml).
diff --git a/guides/deployment/custom-builds.md b/guides/deployment/custom-builds.md
new file mode 100644
index 000000000..19b519fe0
--- /dev/null
+++ b/guides/deployment/custom-builds.md
@@ -0,0 +1,284 @@
+---
+breadcrumbs:
+ - Cookbook
+ - Deployment
+ - Custom Builds
+synopsis: >
+ The guide provides an overview of custom build processes for CAP projects, explaining how to tailor the standard build process to specific project requirements.
+# layout: cookbook
+status: released
+---
+
+# Customizing `cds build`
+
+[[toc]]
+
+## Build Configurations {#build-config}
+
+`cds build` executes _build tasks_ on your project folders to prepare them for deployment. Build tasks compile source files (typically CDS sources) and create the required artifacts, for example, EDMX files, SAP HANA design-time artifacts, and so on. By default, `cds build` dynamically determines the build tasks from the CDS configuration and from the project context. See [build task properties](#build-task-properties) for a concrete list of the different build task types.
+
+The CDS model folders and files used by `cds build` are determined as follows:
+
+- Known root folders, by [default](../../get-started/#project-structure) the folders _db/, srv/, app/_, can also be configured by [_folders.db, folders.srv, folders.app_](../../get-started/#project-structure).
+- The _src_ folder configured for the individual build task.
+- If [Feature toggles](../extensibility/feature-toggles#enable-feature-toggles) are enabled: subfolders of the folder _fts_.
+- CDS Model folders and files defined by the [required services](../../node.js/cds-env#services) of your project. This also includes models used by required built-in CDS services, like [persistent outbox](../../node.js/outbox#persistent-outbox) or [MTX related services](../multitenancy/mtxs#mtx-services-reference).
+
+Feature toggle folders and required built-in service models will also be added if user defined models have already been configured as [_model_ option](#build-task-properties) in your build tasks.
+
+[Learn more about the calculation of the concrete list of CDS models.](../../node.js/cds-compile#cds-resolve){.learn-more}
+
+::: tip If custom build tasks are configured, those properties have precedence
+For example, you want to configure the _src_ folder and add the default models. To achieve this, do not define the _model_ option in your build task. That way, the model paths will still be dynamically determined, but the _src_ folder is taken from the build task configuration. So you benefit from the automatic determination of models, for example, when adding a new external services, or when CAP is changing any built-in service configuration values.
+:::
+
+To control which tasks `cds build` executes, you can add them as part of your [project configuration](../../node.js/cds-env#project-settings) in _package.json_ or _.cdsrc.json_.
+
+## Properties
+
+### Build Task {#build-task-properties}
+
+The following build tasks represent the default configuration dynamically determined by `cds build` for a minimal Node.js project when executing `cds build --production` or `cds build --profile production` :
+
+```json
+{
+ "build": {
+ "target": "gen",
+ "tasks": [
+ { "for": "hana", "src": "db", "options": {"model": ["db","srv"] } },
+ { "for": "nodejs", "src": "srv", "options": {"model": ["db","srv"] } }
+ ]
+ }
+}
+```
+
+::: tip
+The executed build tasks are logged to the command line. You can use them as a blue print β copy & paste them into your CDS configuration and adapt them to your needs. See also the command line help for further details using `cds build --help`.
+:::
+
+The `for` property defines the executed build task type. Currently supported types are:
+
+- `hana`: Creates a deployment layout for the SAP HANA Development Infrastructure (HDI) if an [SAP HANA database](../databases-hana#configure-hana) has been configured.
+- `nodejs` (deprecated: `node-cf`): Creates a deployment layout using a self-contained folder _./gen_ for Node.js apps.
+- `java` (deprecated: `java-cf`): Creates a deployment layout for Java apps.
+- `mtx`: Creates a deployment layout for Node.js applications using multitenancy, feature toggles, extensibility or a combination of these _without_ sidecar architecture.
+ In this scenario the required services are implemented by the Node.js application itself which is the default for Node.js.
+- `mtx-sidecar`: Creates a deployment layout for Java or Node.js projects using multitenancy, feature toggles, extensibility or a combination of these _with_ sidecar architecture.
+ Java projects have to use a sidecar architecture. For Node.js this is optional, but allows for better scalability in multitenant scenarios.
+ [Learn more about **Multitenant Saas Application Deployment**](./to-cf){.learn-more}
+- `mtx-extension`: Creates a deployment layout (_extension.tgz_ file) for an MTX extension project, which is required for extension activation using `cds push`. Extension point restrictions defined by the SaaS app provider are validated by default. If any restriction is violated the build aborts and the errors are logged.
+ The build task is created by default for projects that have `"cds": { "extends": "\" }` configured in their _package.json_.
+ [Learn more about **Extending and Customizing SaaS Solutions**](../extensibility/customization){.learn-more}
+- Additional types may be supported by build plugin contributions.
+ [Learn more about **Running Build Plugins**](#run-the-plugin){.learn-more}
+
+Build tasks can be customized using the following properties:
+
+- `src`: Source folder of the module that is about to be build.
+- `dest`: Optional destination of the modules builds, relative to the enclosing project. The _src_ folder is used by default.
+- `options`: Sets the options according to the target technology.
+ - `model`: It has type _string_ or _array of string_. The given list of folders or individual _.cds_ file names is resolved based on the current working dir or the project folder passed to cds build. CDS built-in models (prefix _@sap/cds*_) are added by default to the user-defined list of models.
+ [Learn more about **Core Data Services (CDS)**](../../cds/){.learn-more}
+
+**Note:**
+Alternatively you can execute build tasks and pass the described arguments from command line. See also `cds build --help` for further details.
+
+### Build Target Folder {#build-target-folder}
+
+If you want to change the default target folder, use the `target` property in _.cdsrc.json_ or _package.json_. It is resolved based on the root folder of your project.
+
+```json
+{
+ "build": { "target" : "myfolder" }
+}
+```
+
+#### Node.js
+
+Node.js projects use the folder _./gen_ below the project root as build target folder by default.
+Relevant source files from _db_ or _srv_ folders are copied into this folder, which makes it a self-contained folder that is ready for deployment. The default folder names can be changed with the _folders.db_, _folders.srv_, _folders.app_ configuration. Or you can go for individual build task configuration for full flexibility.
+
+ Project files like _.cdsrc.json_ or _.npmrc_ located in the _root_ folder or in the _srv_ folder of your project are copied into the application's deployment folder (default _gen/srv_). Files located in the _srv_ folder have precedence over the corresponding files located in the project root directory.
+ As a consequence these files are used when deployed to production. Make sure that the folders do not contain one of these files by mistake. Consider using profiles `development` or `production` in order to distinguish environments. CDS configuration that should be kept locally can be defined in a file _.cdsrc-private.json_.
+
+ The contents of the _node_modules_ folder is _not_ copied into the deployment folder. For security reasons the files _default-env.json_ and _.env_ are also not copied into the deployment folder.
+
+ You can verify the CDS configuration settings that become effective in `production` deployments. Executing `cds env --profile production` in the deployment folder _gen/srv_ will log the CDS configuration used in production environment.
+
+[Learn more about `cds env get`](../../node.js/cds-env#cli){.learn-more}
+
+**Note:**
+`cds build` provides options you can use to switch on or off the copy behavior on build task level:
+
+```json
+{
+ "build": {
+ "tasks": [
+ { "for": "nodejs", "options": { "contentCdsrcJson": false, "contentNpmrc": false } },
+ { "for": "hana", "options": { "contentNpmrc": false } }
+ ]
+ }
+}
+```
+
+#### npm Workspace Support {#build-ws}
+
+Use CLI option `--ws-pack` to enable tarball based deployment of [npm workspace](https://docs.npmjs.com/cli/using-npm/workspaces) dependencies. Workspaces are typically used to manage multiple local packages within a singular top-level root package. Such a setup is often referred to as a [monorepo](https://earthly.dev/blog/npm-workspaces-monorepo/).
+
+As an effect, your workspace dependencies can be deployed to SAP BTP without them being published to an npm registry before.
+
+Behind the scenes, `cds build --ws-pack` creates a tarball in folder _gen/srv_ for each workspace dependency of your project that has a `*` version identifier. Dependencies in _gen/package.json_ will be adapted to point to the correct tarball file URL.
+
+Packaging of the tarball content is based on the rules of the [`npm pack`](https://docs.npmjs.com/cli/commands/npm-pack) command:
+
+- Files and folders defined in _.gitignore_ will not be added
+- If an optional `files` field is defined in the workspace's _package.json_, only those files will be added.
+
+#### Java
+
+Java projects use the project's root folder _./_ as build target folder by default.
+This causes `cds build` to create the build output below the individual source folders. For example, _db/src/gen_ contains the build output for the _db/_ folder. No source files are copied to _db/src/gen_ because they're assumed to be deployed from their original location, the _db/_ folder itself.
+
+## Implement a Build Plugin {#custom-build-plugins}
+
+CDS already offers build plugins to create deployment layouts for the most use cases. However, you find cases where these plugins are not enough and you have to develop your own. This section shows how such a build plugin can be implemented and how it can be used in projects.
+
+Build plugins are run by `cds build` to generate the required deployment artifacts. Build tasks hold the actual project specific configuration. The task's `for` property value has to match the build plugin ID.
+
+**Note:** Minimum version 7.5.0 of `@sap/cds-dk` and `@sap/cds` needs to be installed.
+
+The following description uses the [postgres build plugin](https://github.com/cap-js/cds-dbs/blob/55e511471743c0445d41e8297f5530abe167a270/postgres/cds-plugin.js#L9-L48) as reference implementation. It combines runtime and design-time integration in a single plugin `@cap-js/postgres`.
+
+### Add Build Logic
+
+A build plugin is a Node.js module complying to the [CDS plugin architecture](../../node.js/cds-plugins).
+It must be registered to the CDS build system inside a top-level [cds-plugin.js](https://github.com/cap-js/cds-dbs/blob/main/postgres/cds-plugin.js) file:
+
+::: code-group
+
+```js [cds-plugin.js]
+const cds = require('@sap/cds')
+const { fs, path } = cds.utils;
+
+cds.build?.register?.('postgres', class PostgresBuildPlugin extends cds.build.Plugin {
+ static taskDefaults = { src: cds.env.folders.db }
+ static hasTask() {
+ return cds.requires.db?.kind === 'postgres';
+ }
+ init() {
+ this.task.dest = path.join(this.task.dest, 'pg');
+ }
+ async build() {
+ const model = await this.model();
+ if (!model) return;
+
+ await this.write(cds.compile.to.json(model)).to(path.join('db', 'csn.json'))
+
+ if (fs.existsSync(path.join(this.task.src, 'data'))) {
+ await this.copy(data).to(path.join('db', 'data'))
+ }
+ . . .
+ }
+})
+```
+
+:::
+
+Notes:
+
+- The build plugin id has to be unique. In the previous snippet, the ID is `postgres`.
+- `cds.build` will be `undefined` in non-build CLI scenarios or if the `@sap/cds-dk` package isn't installed (globally or locally as a `devDependency` of the project).
+
+CDS offers a base build plugin implementation, which you can extend to implement your own behavior. The following methods are called by the build system in this sequence:
+
+- `static taskDefaults` - defines default settings for build tasks of this type. For database related plugins the default `src` folder value cds.folders.db: db should be used, while for cds services related plugins cds.folders.srv: srv.
+- `static hasTask()` - determines whether the plugin should be called for the running `cds build` command, returns _true_ by default. This will create a build task with default settings defined by `taskDefaults` and settings calculated by the framework.
+- `init()` - can be used to initialize properties of the plugin, for example, changing the default build output directory defined by the property `dest`.
+- `async clean` - deletes existing build output, folder `this.task.dest` is deleted by default.
+- `async build` - performs the build.
+
+The CDS build system auto-detects all required build tasks by invoking the static method `hasTask` on each registered build plugin.
+
+The compiled CSN model can be accessed using the asynchronous methods `model()` or `basemodel()`.
+
+- The method `model()` returns a CSN model for the scope defined by the `options.model` setting. If [feature toggles](../extensibility/feature-toggles) are enabled, this model also includes any toggled feature enhancements.
+- To get a CSN model without features, use the method `baseModel()` instead. The model can be used as input for further [model processing](../../node.js/cds-compile#cds-compile-to-xyz), like `to.edmx`, `to.hdbtable`, `for.odata`, etc.
+- Use [`cds.reflect`](../../node.js/cds-reflect) to access advanced query and filter functionality on the CDS model.
+
+#### Add build task type to cds schema
+
+**Note:** Minimum version `7.6.0` of `@sap/cds-dk` and `@sap/cds` needs to be installed.
+
+In addition you can also add a new build task type provided by your plugin. This build task type will then be part of code completion suggestions for `package.json` and `.cdsrc.json` files.
+
+[Learn more about schema contributions here.](../../node.js/cds-plugins#configuration-schema){.learn-more}
+
+#### Write Build Output
+
+The `cds.build.Plugin` class provides methods for copying or writing contents to the file system:
+
+::: code-group
+
+```js [postgres/lib/build.js]
+await this.copy(path.join(this.task.src, 'package.json')).to('package.json');
+await this.write({
+ dependencies: { '@sap/cds': '^7', '@cap-js/postgres': '^1' },
+ scripts: { start: 'cds-deploy' }
+}).to('package.json');
+```
+
+:::
+
+These `copy` and `write` methods ensure that build output is consistently reported in the console log. Paths are relative to the build task's `dest` folder.
+
+#### Handle Errors
+
+Messages can be issued using the `pushMessage` method:
+
+::: code-group
+
+```js [postgres/lib/build.js]
+const { Plugin } = cds.build
+const { INFO, WARNING } = Plugin
+
+this.pushMessage('Info message', INFO);
+this.pushMessage('Warning message', WARNING);
+```
+
+:::
+
+These messages are sorted and filtered according to the CLI parameter `log-level`.
+They will be logged after the CDS build has been finished. A `BuildError` can be thrown in case of severe errors.
+In case of any CDS compilation errors, the entire build process is aborted and a corresponding message is logged.
+The `messages` object can be accessed using `this.messages`. When accessing the compiler API it should be passed as an option - otherwise compiler messages won't get reported.
+
+### Run the Plugin
+
+In the application's _package.json_, add a dependency to your plugin package to the list of `devDependencies`.
+> Only use `dependencies` if the plugin also provides _runtime integration_, which is the case for the `@cap-js/postgres` plugin.
+
+::: code-group
+
+```jsonc [package.json]
+"dependencies": {
+ "@cap-js/postgres": "^1"
+}
+```
+
+:::
+
+The CDS build system by default auto-detects all required build tasks. Alternatively, users can run or configure required build tasks in the very same way as for the built-in tasks.
+
+```sh
+cds build
+cds build --for postgres
+```
+
+```json
+"tasks": [
+ { "for": "nodejs" },
+ { "for": "postgres" }
+]
+```
+
+> See also the command line help for further details using `cds build --help`.
diff --git a/guides/deployment/health-checks.md b/guides/deployment/health-checks.md
new file mode 100644
index 000000000..54644400f
--- /dev/null
+++ b/guides/deployment/health-checks.md
@@ -0,0 +1,38 @@
+---
+breadcrumbs:
+ - Cookbook
+ - Deployment
+ - Health Checks
+synopsis: >
+ The guide provides an overview of health checks that are available on Cloud Foundry and Kubernetes, how to configure them, as well as the respective defaults of the two CAP stacks.
+# layout: cookbook
+status: released
+---
+
+# Health Checks
+
+On both Cloud Foundry and Kubernetes, it is possible to provide two separate endpoints for liveness checks ("are you alive?") and readiness checks ("are you ready for more requests?").
+A failure on the former leads to a restart, whereas a failure on the latter temporarily takes the app instance out of the request dispatching rotation until a subsequent readiness probe is successful.
+
+[Learn more about health checks on Cloud Foundry.](https://docs.cloudfoundry.org/devguide/deploy-apps/healthchecks.html) {.learn-more}
+
+[Learn more about health checks on Kubernetes.](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes) {.learn-more}
+
+From `@sap/cds^7.8` onwards, the Node.js runtime provides an out of the box endpoint for liveness and readiness checks at `/health`.
+Requests that reach this public endpoint are answered with the status code 200 and the body `{ status: 'UP' }`.
+
+For adding health checks to your Java app, please see [Spring Boot Health Checks](../../java/operating-applications/observability#spring-health-checks).
+
+To achieve this, from `@sap/cds-dk^7.8` onwards, the configuration for readiness health checks is included in the MTA template for deployment to CF.
+The default configuration specifies checks via http to `/` for Java (as there aren't any default Spring Boot health check endpoints) and `/health` for Node.js, respectively.
+
+Additionally, for Node.js, the Helm chart template now specifies `/health` instead of `/` as the endpoint for the checks. For your app to be considered "alive", the endpoint `/health` needs to return a success response code.
+This means, if you have a _fully custom_ `server.js`, you will need to add the `/health` endpoint to it, or adjust your MTA descriptor/Helm chart (if it's generated with `@sap/cds-dk^7.8`).
+
+::: warning
+Don't forget to adjust the values in your MTA descriptor/Helm chart in case you add the Spring Boot health check endpoints, or use a _fully custom_ `server.js`, etc.!
+:::
+
+[Learn more about availability checks in Node.js.](../../node.js/best-practices#availability-checks) {.learn-more}
+
+[Learn more about availability checks in Java.](../../java/operating-applications/observability#availability) {.learn-more}
diff --git a/guides/deployment/index.data.ts b/guides/deployment/index.data.ts
new file mode 100644
index 000000000..6b4139e33
--- /dev/null
+++ b/guides/deployment/index.data.ts
@@ -0,0 +1,11 @@
+import { basename } from 'node:path'
+import { createContentLoader } from 'vitepress'
+import filter from '../../.vitepress/theme/components/indexFilter.ts'
+
+const basePath = basename(__dirname)
+
+export default createContentLoader(`**/${basePath}/*.md`, {
+ transform(rawData) {
+ return filter(rawData, `/${basePath}/`)
+ }
+})
diff --git a/guides/deployment/index.md b/guides/deployment/index.md
new file mode 100644
index 000000000..dd63d631e
--- /dev/null
+++ b/guides/deployment/index.md
@@ -0,0 +1,22 @@
+---
+index: 80
+breadcrumbs:
+ - Cookbook
+ - Deployment
+synopsis: >
+ Learn here about deployment options of a CAP application.
+status: released
+uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/29c25e504fdb4752b0383d3c407f52a6.html and https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/e4a7559baf9f4e4394302442745edcd9.html
+---
+
+
+
+# Deployment
+
+{{ $frontmatter.synopsis }}
+
+
+
+
diff --git a/guides/deployment/to-cf.md b/guides/deployment/to-cf.md
new file mode 100644
index 000000000..5005006c8
--- /dev/null
+++ b/guides/deployment/to-cf.md
@@ -0,0 +1,482 @@
+---
+index: 70
+breadcrumbs:
+ - Cookbook
+ - Deployment
+ - Deploy to Cloud Foundry
+# shorty: Deploy to CF
+synopsis: >
+ A comprehensive guide on deploying applications built with SAP Cloud Application Programming Model (CAP) to SAP BTP Cloud Foundry environment.
+#notebook: true
+impl-variants: true
+# layout: cookbook
+status: released
+---
+
+# Deploy to Cloud Foundry
+
+{{ $frontmatter.synopsis }}
+
+[[toc]]
+
+## Intro & Overview
+
+After completing the functional implementation of your CAP application by following the [Getting Started](../../get-started/in-a-nutshell) or [Cookbook](../) guides, you would finally deploy it to the cloud for production. The essential steps are illustrated in the following graphic:
+
+{style="margin: 30px auto"}
+
+First, you apply these steps manually in an ad-hoc deployment, as described in this guide. Then, after successful deployment, you automate them using [CI/CD pipelines](cicd).
+
+
+
+## Prerequisites
+
+The following sections are based on a new project that you can create like this:
+
+
+
+```sh
+cds init bookshop --add sample
+cd bookshop
+```
+
+::: details Alternatively, download or clone the sample repository
+
+Exercise the following steps in the `bookshop` subfolder of the [`cloud-cap-samples`](https://github.com/sap-samples/cloud-cap-samples) repo:
+
+```sh
+git clone https://github.com/sap-samples/cloud-cap-samples samples
+cd samples/bookshop
+```
+
+:::
+
+
+
+
+
+```sh
+cds init bookshop --java --add sample
+cd bookshop
+```
+
+> If you want to use a ready-to-be-deployed sample, see our [java/samples](https://github.com/sap-samples/cloud-cap-samples-java).
+
+[Learn more about Setting Up Local Development.](../../java/getting-started#local){.learn-more}
+
+
+
+
+
+In addition, you need to prepare the following:
+
+#### 1. SAP BTP with SAP HANA Cloud Database up and Running {#btp-and-hana}
+
+- Access to [SAP BTP, for example a trial](https://developers.sap.com/tutorials/hcp-create-trial-account.html)
+- An [SAP HANA Cloud database running](https://help.sap.com/docs/hana-cloud/sap-hana-cloud-administration-guide/create-sap-hana-database-instance-using-sap-hana-cloud-central) in your subaccount
+- Entitlement for [`hdi-shared` service plan](https://help.sap.com/docs/hana-cloud/sap-hana-cloud-getting-started-guide/set-up-schema-or-hdi-container-cloud-foundry) for your subaccount
+- A [Cloud Foundry space](https://help.sap.com/docs/btp/sap-business-technology-platform/create-spaces?version=Cloud)
+
+::: tip Starting the SAP HANA database takes several minutes
+Therefore, we recommend doing these steps early on. In trial accounts, you need to start the database **every day**.
+:::
+
+#### 2. Latest Versions of `@sap/cds-dk` {#latest-cds}
+
+Ensure you have the latest version of `@sap/cds-dk` installed globally:
+
+```sh
+npm -g outdated #> check whether @sap/cds-dk is listed
+npm i -g @sap/cds-dk #> if necessary
+```
+
+
+
+Likewise, ensure the latest version of `@sap/cds` is installed in your project:
+
+```sh
+npm outdated #> check whether @sap/cds is listed
+npm i @sap/cds #> if necessary
+```
+
+
+
+#### 3. Cloud MTA Build Tool {#mbt}
+
+- Run `mbt` in a terminal to check whether you've installed it.
+- If not, install it according to the [MTA Build Tool's documentation](https://sap.github.io/cloud-mta-build-tool/download).
+- For macOS/Linux machines best is to install using `npm`:
+
+ ```sh
+ npm i -g mbt
+ ```
+
+- For Windows, [please also install `GNU Make`](https://sap.github.io/cloud-mta-build-tool/makefile/).
+
+#### 4. Cloud Foundry CLI w/ MTA Plugins {#cf-cli}
+
+- Run `cf -v` in a terminal to check whether you've installed version **8** or higher.
+- If not, install or update it according to the [Cloud Foundry CLI documentation](https://github.com/cloudfoundry/cli#downloads).
+- In addition, ensure to have the [MTA plugin for the Cloud Foundry CLI](https://github.com/cloudfoundry-incubator/multiapps-cli-plugin/tree/master/README.md) installed.
+
+ ```sh
+ cf add-plugin-repo CF-Community https://plugins.cloudfoundry.org
+ cf install-plugin multiapps
+ cf install-plugin html5-plugin
+ ```
+
+## Prepare for Production {#prepare-for-production}
+
+If you followed CAP's grow-as-you-go approach so far, you've developed your application with an in-memory database and basic/mock authentication. To prepare for production you need to ensure respective production-grade choices are configured:
+
+{style="margin: 30px auto"}
+
+We'll use the `cds add ` CLI command for that, which ensures the required services are configured correctly and corresponding package dependencies are added to your _package.json_.
+
+### 1. Using SAP HANA Database
+
+
+While we used SQLite as a low-cost stand-in during development, we're going to use a managed SAP HANA database for production:
+
+
+
+While we used SQLite or H2 as a low-cost stand-in during development, we're going to use a managed SAP HANA database for production:
+
+
+```sh
+cds add hana --for production
+```
+
+[Learn more about using SAP HANA for production.](../databases-hana){.learn-more}
+
+### 2. Using XSUAA-Based Authentication
+
+Configure your app for XSUAA-based authentication:
+
+```sh
+cds add xsuaa --for production
+```
+
+::: tip This will also generate an `xs-security.json` file
+The roles/scopes are derived from authorization-related annotations in your CDS models. Ensure to rerun `cds compile --to xsuaa`, as documented in the [_Authorization_ guide](/guides/security/authorization#xsuaa-configuration) whenever there are changes to these annotations.
+:::
+
+::: details For trial and extension landscapes, OAuth configuration is required
+Add the following snippet to your _xs-security.json_ and adapt it to the landscape you're deploying to:
+
+```json
+"oauth2-configuration": {
+ "redirect-uris": ["https://*.cfapps.us10-001.hana.ondemand.com/**"]
+}
+```
+
+:::
+
+[Learn more about SAP Authorization and Trust Management/XSUAA.](https://discovery-center.cloud.sap/serviceCatalog/authorization-and-trust-management-service?region=all){.learn-more}
+
+### 3. Using MTA-Based Deployment { #add-mta-yaml}
+
+We'll be using the [Cloud MTA Build Tool](https://sap.github.io/cloud-mta-build-tool/) to execute the deployment. The modules and services are configured in an `mta.yaml` deployment descriptor file, which we generate with:
+
+```sh
+cds add mta
+```
+
+[Learn more about MTA-based deployment.](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/d04fc0e2ad894545aebfd7126384307c.html?locale=en-US){.learn-more}
+
+### 4. Using App Router as Gateway { #add-app-router}
+
+The _App Router_ acts as a single point-of-entry gateway to route requests to. In particular, it ensures user login and authentication in combination with XSUAA.
+
+Two deployment options are available:
+
+- **Managed App Router**: for SAP Build Work Zone, the Managed App Router provided by SAP Fiori Launchpad is available. See the [end-to-end tutorial](https://developers.sap.com/tutorials/integrate-with-work-zone.html) for the necessary configuration in `mta.yaml` and on each _SAP Fiori application_.
+- **Custom App Router**: for scenarios without SAP Fiori Launchpad, the App Router needs to be deployed along with your application.
+ Use the following command to enhance the application configuration:
+
+ ```sh
+ cds add approuter
+ ```
+
+[Learn more about the SAP BTP Application Router.](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/01c5f9ba7d6847aaaf069d153b981b51.html?locale=en-US){.learn-more}
+
+### 5. User Interfaces { #add-ui }
+
+#### SAP Cloud Portal
+
+If you intend to deploy user interface applications, you also need to set up the [HTML5 Application Repository](https://discovery-center.cloud.sap/serviceCatalog/html5-application-repository-service) in combination with the [SAP Cloud Portal service](https://discovery-center.cloud.sap/serviceCatalog/cloud-portal-service):
+
+```sh
+cds add portal
+```
+
+#### SAP Build Work Zone, Standard Edition
+
+For **single-tenant applications**, you can use [SAP Build Work Zone, Standard Edition](https://discovery-center.cloud.sap/serviceCatalog/sap-build-work-zone-standard-edition):
+
+```sh
+cds add workzone
+```
+
+### 6. Optional: Add Multitenancy { #add-multitenancy }
+
+To enable multitenancy for production, run the following command:
+
+```sh
+cds add multitenancy --for production
+```
+
+> If necessary, modifies deployment descriptors such as _mta.yaml_ for Cloud Foundry.
+
+[Learn more about MTX services.](../multitenancy/#behind-the-scenes){.learn-more}
+
+::: tip You're set!
+The previous steps are required _only once_ in a project's lifetime. With that done, we can repeatedly deploy the application.
+:::
+
+
+
+### 7. Freeze Dependencies { #freeze-dependencies }
+
+
+
+Deployed applications should freeze all their dependencies, including transient ones. Create a _package-lock.json_ file for that:
+
+```sh
+npm update --package-lock-only
+```
+
+
+
+If you use multitenancy, also freeze dependencies for the MTX sidecar:
+
+```sh
+npm update --package-lock-only --prefix mtx/sidecar
+```
+
+In addition, you need install and freeze dependencies for your UI applications:
+
+```sh
+npm i --prefix app/browse
+npm i --prefix app/admin-books
+```
+
+[Learn more about dependency management for Node.js](../../node.js/best-practices#dependencies){.learn-more}
+
+::: tip Regularly update your `package-lock.json` to consume latest versions and bug fixes
+Do so by running this command again, for example each time you deploy a new version of your application.
+:::
+
+## Build & Assemble { #build-mta }
+
+### Build Deployables with `cds build`
+
+Run `cds build` to generate additional deployment artifacts and prepare everything for production in a local `./gen` folder as a staging area. While `cds build` is included in the next step `mbt build`, you can also run it selectively as a test, and to inspect what is generated:
+
+```sh
+cds build --production
+```
+
+[Learn more about running and customizing `cds build`.](custom-builds){.learn-more}
+
+### Assemble with `mbt build`
+
+::: info Prepare monorepo setups
+The CAP samples repository on GitHub has a more advanced (monorepo) structure, so tell the `mbt` tool to find the `package-lock.json` on top-level:
+
+```sh
+ln -sf ../package-lock.json
+```
+
+:::
+
+Now, we use the `mbt` build tool to assemble everything into a single `mta.tar` archive:
+
+```sh
+mbt build -t gen --mtar mta.tar
+```
+
+[Got errors? See the troubleshooting guide.](../../get-started/troubleshooting#mta){.learn-more}
+
+[Learn how to reduce the MTA archive size during development.](../../get-started/troubleshooting#reduce-mta-size){.learn-more}
+
+## Deploy to Cloud {#deploy}
+
+Finally, we can deploy the generated archive to Cloud Foundry:
+
+```sh
+cf deploy gen/mta.tar
+```
+
+[You need to be logged in to Cloud Foundry.](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/7a37d66c2e7d401db4980db0cd74aa6b.html){.learn-more}
+
+This process can take some minutes and finally creates a log output like this:
+
+```log
+[β¦]
+Application "bookshop" started and available at
+"[org]-[space]-bookshop.landscape-domain.com"
+[β¦]
+```
+
+Copy and open this URL in your web browser. It's the URL of your App Router application.
+
+::: tip For multitenant applications, you have to subscribe a tenant first
+In this case, the application is accessible via a tenant-specific URL after onboarding.
+:::
+
+### Inspect Apps in BTP Cockpit
+
+Visit the "Applications" section in your [SAP BTP cockpit](https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/144e1733d0d64d58a7176e817fa6aeb3.html) to see the deployed apps:
+
+{.mute-dark}
+
+::: tip Assign the _admin_ role
+We didn't do the _admin_ role assignment for the `AdminService`. You need to create a role collection and [assign the role and your user](https://developers.sap.com/tutorials/btp-app-role-assignment.html) to get access.
+:::
+
+[Got errors? See the troubleshooting guide.](../../get-started/troubleshooting#cflogs-recent){.learn-more}
+
+### Upgrade Tenants {.java}
+
+The CAP Java SDK offers `main` methods for Subscribe/Unsubscribe in the classes `com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe` that can be called from the command line. This way, you can run the tenant subscribe/unsubscribe for the specified tenant. This would trigger also your custom handlers, which is useful for the local testing scenarios.
+
+In order to register all handlers of the application properly during the execution of a tenant operation `main` method, the component scan package must be configured. To set the component scan, the property cds.multitenancy.component-scan must be set to the package name of your application.
+
+The handler registration provides additional information that is used for the tenant subscribe, for example, messaging subscriptions that are created.
+
+::: warning
+You can stop the CAP Java back end when you call this method, but the _MTX Sidecar_ application must be running!
+:::
+
+This synchronization can also be automated, for example using [Cloud Foundry Tasks](https://docs.cloudfoundry.org/devguide/using-tasks.html) on SAP BTP and [Module Hooks](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/b9245ba90aa14681a416065df8e8c593.html) in your MTA.
+
+The `main` method optionally takes tenant ID (string) as the first input argument and tenant options (JSON string) as the second input argument. Alternatively, you can use the environment variables `MTCOMMAND_TENANTS` and `MTCOMMAND_OPTIONS` instead of arguments. The command-line arguments have higher priority, so you can use them to override the environment variables.
+
+The method returns the following exit codes.
+
+| Exit Code | Result |
+| --------- | ------------------------------------------------------------------------------------------------ |
+| 0 | Tenant subscribed/unsubscribed successfully. |
+| 3 | Failed to subscribe/unsubscribe the tenant. Rerun the procedure to make sure the tenant is subscribed/unsubscribed. |
+
+To run this method locally, use the following command where `` is the one of your applications:
+
+::: code-group
+
+```sh [>= Spring Boot 3.2.0]
+java -cp -Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe org.springframework.boot.loader.launch.PropertiesLauncher []
+```
+
+```sh [< Spring Boot 3.2.0]
+java -cp -Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe org.springframework.boot.loader.PropertiesLauncher []
+```
+
+:::
+
+In the SAP BTP, Cloud Foundry environment, it can be tricky to construct such a command. The reason is that the JAR file is extracted by the Java buildpack and the place of the Java executable isn't easy to determine. Also the place differs for different Java versions. Therefore, we recommend adapting the start command that is generated by the buildpack and run the adapted command:
+
+::: code-group
+
+```sh [>= Spring Boot 3.2.0]
+sed -i 's/org.springframework.boot.loader.launch.JarLauncher/org.springframework.boot.loader.launch.PropertiesLauncher/g' /home/vcap/staging_info.yml && sed -i 's/-Dsun.net.inetaddr.negative.ttl=0/-Dsun.net.inetaddr.negative.ttl=0 -Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe/g' /home/vcap/staging_info.yml && jq -r .start_command /home/vcap/staging_info.yml | sed 's/^/ MTCOMMAND_TENANTS=my-tenant [MTCOMMAND_TENANTS=]/' | bash
+```
+
+```sh [< Spring Boot 3.2.0]
+sed -i 's/org.springframework.boot.loader.JarLauncher/org.springframework.boot.loader.PropertiesLauncher/g' /home/vcap/staging_info.yml && sed -i 's/-Dsun.net.inetaddr.negative.ttl=0/-Dsun.net.inetaddr.negative.ttl=0 -Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe/g' /home/vcap/staging_info.yml && jq -r .start_command /home/vcap/staging_info.yml | sed 's/^/ MTCOMMAND_TENANTS=my-tenant [MTCOMMAND_TENANTS=]/' | bash
+```
+
+```sh [Java 8]
+sed -i 's/org.springframework.boot.loader.JarLauncher/-Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe org.springframework.boot.loader.PropertiesLauncher/g' /home/vcap/staging_info.yml && jq -r .start_command /home/vcap/staging_info.yml | sed 's/^/ MTCOMMAND_TENANTS=my-tenant [MTCOMMAND_TENANTS=]/' | bash
+```
+
+:::
+
+---
+{style="margin-top:11em"}
+
+# Appendices
+
+## Deploy using `cf push`
+
+As an alternative to MTA-based deployment, you can choose Cloud Foundry-native deployment using [`cf push`](https://docs.cloudfoundry.org/devguide/push.html), or `cf create-service-push` respectively.
+
+### Prerequisites
+
+Install the [_Create-Service-Push_ plugin](https://github.com/dawu415/CF-CLI-Create-Service-Push-Plugin):
+
+```sh
+cf install-plugin Create-Service-Push
+```
+
+This plugin acts the same way as `cf push`, but extends it such that services are _created_ first. With the plain `cf push` command, this is not possible.
+
+### Add a `manifest.yml` {#add-manifest}
+
+```sh
+cds add cf-manifest
+```
+
+This creates two files, a _manifest.yml_ and _services-manifest.yml_ in the project root folder.
+
+- _manifest.yml_ holds the applications. In the default layout, one application is the actual server holding the service implementations, and the other one is a 'DB deployer' application, whose sole purpose is to start the SAP HANA deployment.
+- _services-manifest.yml_ defines which Cloud Foundry services shall be created. The services are derived from the service bindings in _package.json_ using the [`cds.requires` configuration](../../node.js/cds-env#services).
+
+::: tip Version-control manifest files
+Unlike the files in the _gen_ folders, these manifest files are genuine sources and should be added to the version control system. This way, you can adjust them to your needs as you evolve your application.
+:::
+
+### Build the Project
+
+This prepares everything for deployment, and -- by default -- writes the build output, that is the deployment artifacts, to folder _./gen_ in your project root.
+
+```sh
+cds build --production
+```
+
+[Learn how `cds build` can be configured.](custom-builds#build-config){.learn-more}
+
+The `--production` parameter ensures that the cloud deployment-related artifacts are created by `cds build`. See section [SAP HANA database deployment](../databases-hana) for more details.
+
+### Push the Application { #push-the-application}
+
+This command creates service instances, pushes the applications and binds the services to the application with a single call:
+
+```sh
+cf create-service-push
+```
+
+During deployment, the plugin reads the _services-manifest.yml_ file and creates the services listed there. It then reads _manifest.yml_, pushes the applications defined there, and binds these applications to service instances created before. If the service instances already exist, only the `cf push` operation will be executed.
+
+You can also apply some shortcuts:
+
+- Use `cf push` directly to deploy either all applications, or `cf push ` to deploy a single application.
+- Use `cf create-service-push --no-push` to only create or update service-related data without pushing the applications.
+
+In the deployment log, find the application URL in the `routes` line at the end:
+
+```log{3}
+name: bookshop-srv
+requested state: started
+routes: bookshop-srv.cfapps.sap.hana.ondemand.com
+```
+
+Open this URL in the browser and try out the provided links, for example, `β¦/browse/Books`. Application data is fetched from SAP HANA.
+::: tip Ensure successful SAP HANA deployment
+Check the deployment logs of the database deployer application using
+
+```sh
+cf logs -db-deployer --recent
+```
+
+to ensure that SAP HANA deployment was successful. The application itself is by default in state `started` after HDI deployment has finished, even if the HDI deployer returned an error. To save resources, you can explicitly stop the deployer application afterwards.
+:::
+::: tip No Fiori preview in the cloud
+The [SAP Fiori Preview](../../advanced/fiori#sap-fiori-preview), that you are used to see from local development, is only available for the development profile and not available in the cloud. For productive applications, you should add a proper SAP Fiori application.
+:::
+
+::: warning
+Multitenant applications are not supported yet as multitenancy-related settings are not added to the generated descriptors. The data has to be entered manually.
+:::
+
+[Got errors? See the troubleshooting guide.](../../get-started/troubleshooting#aborted-deployment-with-the-create-service-push-plugin){.learn-more}
diff --git a/guides/deployment/to-kyma.md b/guides/deployment/to-kyma.md
new file mode 100644
index 000000000..19c72e90a
--- /dev/null
+++ b/guides/deployment/to-kyma.md
@@ -0,0 +1,658 @@
+---
+label: Deploy to Kyma/K8s
+synopsis: >
+ A step-by-step guide on how to deploy a CAP (Cloud Application Programming Model) application to Kyma Runtime of SAP Business Technology Platform.
+breadcrumbs:
+ - Cookbook
+ - Deployment
+ - Deploy to Kyma
+impl-variants: true
+status: released
+# uacp: Used as link target from Help Portal at https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/29c25e504fdb4752b0383d3c407f52a6.html and https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/e4a7559baf9f4e4394302442745edcd9.html
+---
+
+
+
+
+# Deploy to Kyma Runtime
+
+You can run your CAP application in the [Kyma Runtime](https://discovery-center.cloud.sap/serviceCatalog/kyma-runtime?region=all). This runtime of the SAP Business Technology Platform is the SAP managed offering for the [Kyma project](https://kyma-project.io/). This guide helps you to run your CAP applications on SAP BTP Kyma Runtime.
+
+
+
+[[toc]]
+
+## Overview
+
+As well as Kubernetes, Kyma is a platform to run containerized workloads. The service's files are provided as a container image, commonly referred to as a Docker image. In addition, the containers to be run on Kubernetes, their configuration and everything else that is needed to run them, are described by Kubernetes resources.
+
+In consequence, two kinds of artifacts are needed to run applications on Kubernetes:
+
+1. Container images
+2. Kubernetes resources
+
+The following diagram shows the steps to run on the SAP BTP Kyma Runtime:
+
+
+
+1. [**Add** a Helm chart](#cds-add-helm)
+2. [**Build** container images](#build-images)
+3. [**Deploy** your application by applying Kubernetes resources](#deploy-helm-chart)
+
+## Prerequisites {#prerequisites}
+
++ You prepared your project as described in the [Deploy to Cloud Foundry](to-cf) guide.
++ Use a Kyma enabled [Trial Account](https://account.hanatrial.ondemand.com/) or [learn how to get access to a Kyma cluster](#get-access-to-a-cluster).
++ You need a [Container Image Registry](#get-access-to-a-container-registry)
++ Get the required SAP BTP service entitlements
++ Download and install the following command line tools:
+ + [`kubectl` command line client](https://kubernetes.io/docs/tasks/tools/) for Kubernetes
+ + [Docker Desktop or Docker for Linux](https://docs.docker.com/get-docker/)
+ + [`pack` command line tool](https://buildpacks.io/docs/tools/pack/)
+ + [`helm` command line tool](https://helm.sh/docs/intro/install/)
+ + [`ctz` command line tool](https://www.npmjs.com/package/ctz)
+
+::: warning
+Make yourself familiar with Kyma and Kubernetes. CAP doesn't provide consulting on it.
+:::
+
+## Prepare for Production
+
+The detailed procedure is described in the [Deploy to Cloud Foundry guide](to-cf#prepare-for-production). Run this command to fast-forward:
+
+```sh
+cds add hana,xsuaa --for production
+```
+
+## Add Helm Chart {#cds-add-helm}
+
+CAP provides a configurable [Helm chart](https://helm.sh/) for Node.js and Java applications.
+
+```sh
+cds add helm
+```
+
+This command adds the Helm chart to the _chart_ folder of your project with 3 files: `values.yaml`, `Chart.yaml` and `values.schema.json`.
+
+During cds build, the _gen_/_chart_ folder is generated. This folder will have all the necessary files required to deploy the helm chart. Files from the _chart_ folder in root of the project are copied to the folder generated in _gen_ folder.
+
+The files in the _gen/chart_ folder support the deployment of your CAP service, database and UI content, and the creation of instances for BTP services.
+
+[Learn more about CAP Helm chart.](#about-cap-helm){.learn-more}
+
+## Build Images {#build-images}
+
+We'll be using the [Containerize Build Tool](https://www.npmjs.com/package/ctz/) to build the images. The modules are configured in a `containerize.yaml` descriptor file, which we generate with:
+
+```sh
+cds add containerize
+```
+
+#### Configure Image Repository
+
+Specify the repository where you want to push the images:
+
+```yaml
+...
+repository:
+```
+
+::: warning
+You should be logged in to the above repository to be able to push images to it. You can use `docker login -u ` to login.
+:::
+
+Now, we use the `ctz` build tool to build all the images:
+
+```sh
+ctz containerize.yaml
+```
+
+> This will start containerizing your modules based on the configuration in the specified file. After it is done, it will ask whether you want to push the images or not. Type `y` and press enter to push your images. You can also use the above command with `--push` flag to skip this. If you want more logs, you can use the `--log` flag with the above command.
+
+[Learn more about Containerize Build Tool](https://www.npmjs.com/package/ctz/){.learn-more}
+
+### UI Deployment
+
+For UI access, you can use the standalone and the managed App Router as explained in [this blog](https://blogs.sap.com/2021/12/09/using-sap-application-router-with-kyma-runtime/).
+
+The `cds add helm` command [supports deployment](#html5-applications) to the [HTML5 application repository](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/f8520f572a6445a7bfaff4a1bbcbe60a.html?locale=en-US&version=Cloud) which can be used with both options.
+
+For that, create a container image with your UI files configured with the [HTML5 application deployer](https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/9b178ab3388c4647b0c52f2c85641844.html).
+
+The `cds add helm` command also supports deployment of standalone approuter.
+
+To configure backend destinations, have a look at the [approuter configuration section.](#configure-approuter-specifications)
+
+## Deploy Helm Chart {#deploy-helm-chart}
+
+Once your Helm chart is created, your container images are uploaded to a registry and your cluster is prepared, you're almost set for deploying your Kyma application.
+
+### Create Service Instances for SAP HANA Cloud {#hana-cloud-instance}
+
+1. Enable SAP HANA for your project as explained in the [CAP guide for SAP HANA](../databases-hana).
+2. Create an SAP HANA database.
+3. To create HDI containers from Kyma, you need to [create a mapping between your namespace and SAP HANA Cloud instance](https://blogs.sap.com/2022/12/15/consuming-sap-hana-cloud-from-the-kyma-environment/).
+
+::: warning Set trusted source IP addresses
+Make sure that your SAP HANA Cloud instance can be accessed from your Kyma cluster by [setting the trusted source IP addresses](https://help.sap.com/docs/HANA_CLOUD/9ae9104a46f74a6583ce5182e7fb20cb/0610e4440c7643b48d869a6376ccaecd.html).
+:::
+
+### Deploy using CAP Helm Chart
+
+Before deployment, you need to set the container image and cluster specific settings.
+
+#### Configure Access to Your Container Images
+
+Add your container image settings to your _chart/values.yaml_:
+
+```yaml
+...
+global:
+ domain:
+ imagePullSecret:
+ name:
+ image:
+ registry:
+ tag: latest
+```
+
+You can use the pre-configured domain name for your Kyma cluster:
+
+```yaml
+kubectl get gateway -n kyma-system kyma-gateway \
+ -o jsonpath='{.spec.servers[0].hosts[0]}'
+```
+
+To use images on private container registries you need to [create an image pull secret](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/).
+
+For image registry, use the same value you mentioned in `containerize.yaml`
+
+#### Configure Approuter Specifications
+
+By default `srv-api` and `mtx-api` (only in Multi Tenant Application) are configured. If you're using any other destination or your `xs-app.json` file has a different destination, update the destinations under the `backendDestinations` key in _values.yaml_ file:
+
+```yaml
+backendDestinations:
+ backend:
+ service: srv
+```
+
+> `backend` is the name of the destination. `service` points to the deployment name whose url will be used for this destination.
+
+#### Deploy CAP Helm Chart
+
+1. Execute `cds build --production` to generate the helm chart in _gen_ folder.
+2. Deploy using `helm` command:
+
+ ```sh
+ helm upgrade --install bookshop ./gen/chart \
+ --namespace bookshop-namespace
+ --create-namespace
+ ```
+
+ This installs the Helm chart from the _gen/chart_ folder with the release name `bookshop` in the namespace `bookshop-namespace`.
+
+ ::: tip
+ With the `helm upgrade --install` command you can install a new chart as well as upgrade an existing chart.
+ :::
+
+This process can take a few minutes to complete and create the log output:
+
+```log
+[β¦]
+The release bookshop is installed in namespace [namespace].
+
+Your services are available at:
+ [workload] - https://bookshop-[workload]-[namespace].[configured-domain]
+[β¦]
+```
+
+Copy and open this URL in your web browser. It's the URL of your application.
+
+::: info
+ If a standalone approuter is present, the srv and sidecar aren't exposed and only the approuter URL will be logged. But if an approuter isn't present then srv and sidecar are also exposed and their URL will also be logged.
+:::
+
+[Learn more about using a private registry with your Kyma cluster.](#setup-your-cluster-for-a-private-container-registry){.learn-more} [Learn more about the CAP Helm chart settings](#configure-helm-chart){ .learn-more} [Learn more about using `helm upgrade`](https://helm.sh/docs/helm/helm_upgrade){ .learn-more}
+
+::: tip
+Try out the [CAP SFLIGHT](https://github.com/SAP-samples/cap-sflight)
+and [CAP for Java](https://github.com/SAP-samples/cloud-cap-samples-java) examples on Kyma.
+:::
+
+## Customize Helm Chart {#customize-helm-chart}
+
+### About CAP Helm Chart { #about-cap-helm}
+
+The following files are added to a _chart_ folder by executing `cds add helm`:
+
+| File/Pattern | Description |
+| --------------------- | ---------------------------------------------------------- |
+| _values.yaml_ | [Configuration](#configure-helm-chart) of the chart; The initial configuration is determined from your CAP project. |
+| _Chart.yaml_ | Chart metadata that is initially determined from the _package.json_ file |
+| _values.schema.json_ | JSON Schema for _values.yaml_ file |
+
+The following files are added to a _gen/chart_ folder along with all the files in the _chart_ folder in the root of the project by executing `cds build` after adding `helm`:
+
+| File/Pattern | Description |
+| --------------------- | ---------------------------------------------------------- |
+| _templates/*.tpl_ | Template libraries used in the template resources |
+| _templates/NOTES.txt_ | Message printed after installing or upgrading the Helm charts |
+| _templates/*.yaml_ | Template files for the Kubernetes resources |
+
+[Learn how to create a Helm chart from scratch from the Helm documentation.](https://helm.sh/docs){.learn-more}
+
+### Configure {#configure-helm-chart}
+
+[CAP's Helm chart](#cds-add-helm) can be configured by the settings as explained below. Mandatory settings are marked with .
+
+You can change the configuration by editing the _chart/values.yaml_ file. When you call `cds add helm` again, your changes will be persisted and only missing default values are added.
+
+The `helm` CLI also offers you other options to overwrite settings from _chart/values.yaml_ file:
+
++ Overwrite properties using the `--set` parameter.
++ Overwrite properties from a YAML file using the `-f` parameter.
+
+::: tip
+It is recommended to do the main configuration in the _chart/values.yaml_ file and have additional YAML files for specific deployment types (dev, test, productive) and targets.
+:::
+
+#### Global Properties
+
+| Property | Description | Mandatory |
+| --------------- | ------------------------------------------------------------- | :---------: |
+| imagePullSecret → name | Name of secret to access the container registry | ( ) 1 |
+| domain | Kubernetes cluster ingress domain (used for application URLs) | |
+| image → registry | Name of the container registry from where images are pulled | |
+
+1: Mandatory only for private docker registries
+
+#### Deployment Properties
+
+The following properties are available for the `srv` key:
+
+| Property | Description | Mandatory |
+|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------:|
+| **bindings** | [Service Bindings](#configuration-options-for-service-bindings) | |
+| **resources** | [Kubernetes Container resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) | |
+| **env** | Map of additional env variables | |
+| **health** | [Kubernetes Liveness, Readyness and Startup Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | |
+| → liveness → path | Endpoint for liveness and startup probe | |
+| → readiness → path | Endpoint for readiness probe | |
+| → startupTimeout | Wait time in seconds until the health checks are started | |
+| **image** | [Container image](#configuration-options-for-container-images) | |
+
+You can explore more configuration options in the subchart's directory _gen/chart/charts/web-application_.
+
+#### SAP BTP Services
+
+The helm chart supports to create service instances for commonly used services. Services are pre-populated in the _chart/values.yaml_ file based on the used services in the `requires` section of the CAP configuration (for example, _package.json_) file.
+
+You can use the following services in your configuration:
+
+| Property | Description | Mandatory |
+|----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|:---------:|
+| **xsuaa** | Enables the creation of a XSUAA service instance. See details for [Node.js](../../node.js/authentication) and [Java](../../java/security) projects. | |
+| parameters → xsappname | Name of XSUAA application. Overwrites the value from the _xs-security.json_ file. (unique per subaccount) | |
+| parameters → HTML5Runtime_enabled | Set to true for use with Launchpad Service | |
+| **connectivity** | Enables [on-premise connectivity](#connectivity-service) | |
+| **event-mesh** | Enables SAP Event Mesh; [messaging guide](../messaging/), [how to enable the SAP Event Mesh](../messaging/event-mesh) | |
+| **html5-apps-repo-host** | HTML5 Application Repository | |
+| **hana** | HDI Shared Container | |
+| **service-manager** | Service Manager Container | |
+| **saas-registry** | SaaS Registry Service | |
+
+[Learn how to configure services in your Helm chart](#configuration-options-for-services){.learn-more}
+
+#### SAP HANA
+
+The deployment job of your database content to a HDI container can be configured using the `hana-deployer` section with the following properties:
+
+| Property | Description | Mandatory |
+|---------------|------------------------------------------------------------------------------------------------------------------|:---------:|
+| **bindings** | [Service binding](#configuration-options-for-service-bindings) to the HDI container's secret | |
+| **image** | [Container image](#configuration-options-for-container-images) of the HDI deployer | |
+| **resources** | [Kubernetes Container resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) | |
+| **env** | Map of additional environment variables | |
+
+#### HTML5 Applications
+
+The deployment job of HTML5 applications can be configured using the `html5-apps-deployer` section with the following properties:
+
+[Container image]: #configuration-options-for-container-images
+[HTML5 application deployer]: https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/9b178ab3388c4647b0c52f2c85641844.html
+[Kubernetes Container resources]: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
+
+| Property | Description | Mandatory |
+|--------------------------|---------------------------------------------------------------------------------------------------------------------------------------|:---------:|
+| **image** | [Container image] of the [HTML5 application deployer] | |
+| **bindings** | [Service bindings](#configuration-options-for-service-bindings) to XSUAA, destinations and HTML5 Application Repository Host services | |
+| **resources** | [Kubernetes Container resources] | |
+| **env** | Map of additional environment variables | |
+| → SAP_CLOUD_SERVICE | Name for your business service (unique per subaccount) | |
+
+::: tip
+Run `cds add html5-repo` to automate the setup for HTML5 application deployment.
+:::
+
+#### Backend Destinations
+
+Backend destinations maybe required for HTML5 applications or for App Router deployment. They can be configured using the `backendDestinations` section with the following properties:
+
+| Property | Description |
+|------------------|-----------------------------------------------------|
+| (key) | Name of backend destination |
+| service: (value) | Value is the target Kubernetes service (like `srv`) |
+
+If you want to add an external destination, you can do so by providing the `external` property like this:
+
+```yaml
+...
+backendDestinations:
+ srv-api:
+ service: srv
+ ui5: # [!code ++]
+ external: true # [!code ++]
+ name: ui5 # [!code ++]
+ Type: HTTP # [!code ++]
+ proxyType: Internet # [!code ++]
+ url: https://ui5.sap.com # [!code ++]
+ Authentication: NoAuthentication # [!code ++]
+```
+
+> Our helm chart will remove the `external` key and add the rest of the keys as-is to the environment variable.
+
+#### Connectivity Service
+
+Use `cds add connectivity`, to add a volume to your `srv` deployment.
+::: warning
+Create an instance of the SAP BTP Connectivity service with plan `connectivity_proxy` and a service binding, before deploying the first application that requires it. Using this plan, a proxy to the connectivity service gets installed into your Kyma cluster. This may take a few minutes. The connectivity proxy uses the first created instance in a cluster for authentication. This instance must not be deleted as long as connectivity is used.
+:::
+
+The volume you've added to your `srv` deployment is needed, to add additional connection information, compared to what's available from the service binding.
+
+```yaml
+srv:
+...
+ additionalVolumes:
+ - name: connectivity-secret
+ volumeMount:
+ mountPath: /bindings/connectivity
+ readOnly: true
+ projected:
+ sources:
+ - secret:
+ name:
+ optional: false
+ - secret:
+ name:
+ optional: false
+ items:
+ - key: token_service_url
+ path: url
+ - configMap:
+ name: "RELEASE-NAME-connectivity-proxy-info"
+ optional: false
+```
+
+In the volumes added, replace the value of `` with the binding that you created earlier. If the binding is created in a different namespace then you need to create a secret with details from the binding and use that secret.
+::: tip
+You don't have to edit `RELEASE-NAME` in the `configMap` property. It is passed as a template string and will be replaced with your actual release name by Helm.
+:::
+
+#### Arbitrary Service
+
+These are the steps to create and bind to an arbitrary service, using the binding of the feature toggle service to the CAP application as an example:
+
+1. In the _chart/Chart.yaml_ file, add an entry to the `dependencies` array.
+
+ ```yaml
+ dependencies:
+ ...
+ - name: service-instance
+ alias: feature-flags
+ version: 0.1.0
+ ```
+
+2. Add the service configuration and the binding in the _chart/values.yaml_ file:
+
+ ```yaml
+ feature-flags:
+ serviceOfferingName: feature-flags
+ servicePlanName: lite
+ ...
+ srv:
+ bindings:
+ feature-flags:
+ serviceInstanceName: feature-flags
+ ```
+
+ > The `alias` property in the `dependencies` array must match the property added in the root of _chart/values.yaml_ and the value of `serviceInstanceName` in the binding.
+::: warning
+There should be at least one service instance created by `cds add helm` if you want to bind an arbitrary service.
+:::
+
+#### Configuration Options for Services
+
+_Services have the following configuration options:_
+
+| Property | Type | Description | Mandatory
+| ------------------- | ----------- | ---------------------------------------- |:-----: |
+| **fullNameOverride** | string | Use instead of the generated name |
+| **serviceOfferingName** | string | Technical service offering name from service catalog |
+| **servicePlanName** | string | Technical service plan name from service catalog |
+| **externalName** | string | The name for the service instance in SAP BTP |
+| **customTags** | array of string | List of custom tags describing the service instance, will be copied to `ServiceBinding` secret in the key called `tags` |
+| **parameters** | object | Object with service parameters |
+| **jsonParameters** | string | Some services support the provisioning of additional configuration parameters. For the list of supported parameters, check the documentation of the particular service offering. |
+| **parametersFrom** | array of object | List of secrets from which parameters are populated. |
+
+The `jsonParameters` key can also be specified using the `--set file` flag while installing/upgrading Helm release. For example, `jsonParameters` for the `xsuaa` property can be defined using the following command:
+
+```sh
+helm install bookshop ./chart --set-file xsuaa.jsonParameters=xs-security.json
+```
+
+You can explore more configuration options in the subchart's directory _gen/chart/charts/service-instance_.
+
+#### Configuration Options for Service Bindings
+
+| Property | Description | Mandatory |
+|-------------------------|--------------------------------------------------|:------------------:|
+| (key) | Name of the service binding | |
+| secretFrom | Bind to Kubernetes secret | ()1 |
+| serviceInstanceName | Bind to service instance within the Helm chart | ()1 |
+| serviceInstanceFullname | Bind to service instance using the absolute name | ()1 |
+| parameters | Object with service binding parameters | |
+
+1: Exactly one of these properties need to be specified
+
+#### Configuration Options for Container Images
+
+| Property | Description | Mandatory |
+|------------|-------------------------------------------------|:---------:|
+| repository | Full container image repository name | |
+| tag | Container image version tag (default: `latest`) | |
+
+
+
+### Modify
+
+Modifying the Helm chart allows you to customize it to your needs. However, this has consequences if you want to update with the latest changes from the CAP template.
+
+You can run `cds add helm` again to update your Helm chart. It has the following behavior for modified files:
+
+1. Your changes of the _chart/values.yaml_ and _chart/Chart.yaml_ will not be modified. Only new or missing properties will be added by `cds add helm`.
+2. To modify any of the generated files such as templates or subcharts, copy the files from _gen/chart_ folder and place it in the same level inside the _chart_ folder. After the next `cds build` executions the generated chart will have the modified files.
+3. If you want to have some custom files such as templates or subcharts, you can place them in the _chart_ folder at the same level where you want them to be in _gen/chart_ folder. They will be copied as is.
+
+### Extend
+
+1. Adding new files to the Helm chart does not conflict with `cds add helm`.
+2. A modification-free approach to change files is to use [Kustomize](https://kustomize.io/) as a [post-processor](https://helm.sh/docs/topics/advanced/#post-rendering) for your Helm chart. This might be usable for small changes if you don't want to branch-out from the generated `cds add helm` content.
+
+## Additional Information
+
+### SAP BTP Services and Features
+
+You can find a list of SAP BTP services in the [Discovery Center](https://discovery-center.cloud.sap/viewServices?provider=all®ions=all&showFilters=true). To find out if a service is supported in the Kyma and Kubernetes environment, goto to the **Service Marketplace** of your Subaccount in the SAP BTP Cockpit and select Kyma or Kubernetes in the environment filter.
+
+You can find information about planned SAP BTP, Kyma Runtime features in the [product road map](https://roadmaps.sap.com/board?PRODUCT=73554900100800003012&PRODUCT=73554900100800003012).
+
+### Using Service Instance created on Cloud Foundry
+
+To bind service instances created on Cloud Foundry to a workload (`srv`, `hana-deployer`, `html5-deployer`, `approuter` or `sidecar`) in Kyma environment, do the following:
+
+1. In your cluster, create a secret with credentials from the service key of that instance.
+
+2. Use the `fromSecret` property inside the `bindings` key of the workload.
+
+For example, if you want to use an `hdi-shared` instance created on Cloud Foundry:
+
+1. [Create a Kubernetes secret](https://kubernetes.io/docs/concepts/configuration/secret/#creating-a-secret) with the credentials from a service key from the Cloud Foundry account.
+2. Add additional properties to the Kubernetes secret.
+
+ ```yaml
+ stringData:
+ # <β¦>
+ .metadata: |
+ {
+ "credentialProperties":
+ [
+ { "name": "certificate", "format": "text"},
+ { "name": "database_id", "format": "text"},
+ { "name": "driver", "format": "text"},
+ { "name": "hdi_password", "format": "text"},
+ { "name": "hdi_user", "format": "text"},
+ { "name": "host", "format": "text"},
+ { "name": "password", "format": "text"},
+ { "name": "port", "format": "text"},
+ { "name": "schema", "format": "text"},
+ { "name": "url", "format": "text"},
+ { "name": "user", "format": "text"}
+ ],
+ "metaDataProperties":
+ [
+ { "name": "plan", "format": "text" },
+ { "name": "label", "format": "text" },
+ { "name": "type", "format": "text" },
+ { "name": "tags", "format": "json" }
+ ]
+ }
+ type: hana
+ label: hana
+ plan: hdi-shared
+ tags: '[ "hana", "database", "relational" ]'
+ ```
+
+::: tip
+Update the values of the properties accordingly.
+:::
+
+3. Change the `serviceInstanceName` property to `fromSecret` from each workload which has that service instance in `bindings` in _chart/values.yaml_ file:
+
+ ::: code-group
+
+ ```yaml [srv]
+ β¦
+ srv:
+ bindings:
+ db:
+ serviceInstanceName: // [!code --]
+ fromSecret: // [!code ++]
+ ```
+
+ ```yaml [hana-deployer]
+ β¦
+ hana-deployer:
+ bindings:
+ hana:
+ serviceInstanceName: // [!code --]
+ fromSecret: // [!code ++]
+ ```
+
+ :::
+
+4. Delete `hana` property in _chart/values.yaml_ file.
+
+ ::: code-group
+
+ ```yaml
+ β¦
+ hana: // [!code --]
+ serviceOfferingName: hana // [!code --]
+ servicePlanName: hdi-shared // [!code --]
+ β¦
+ ```
+
+ :::
+
+5. Make the following changes to _chart/Chart.yaml_ file.
+
+ ::: code-group
+
+ ```yaml
+ β¦
+ dependencies:
+ β¦
+ - name: service-instance // [!code --]
+ alias: hana // [!code --]
+ version: ">0.0.0" // [!code --]
+ β¦
+ ```
+
+ :::
+
+### About Cloud Native Buildpacks
+
+Cloud Native Buildpacks provide advantages such as embracing [best practices](https://buildpacks.io/features/) and secure standards like:
+
++ Resulting images use an unprivileged user.
++ Builds are [reproducible](https://buildpacks.io/docs/features/reproducibility/).
++ [Software Bill of Materials](https://buildpacks.io/docs/features/bill-of-materials/) (SBoM) for all dependencies baked into the image.
++ Auto detection, no need to manually select base images.
+
+Additionally Cloud Native Buildpacks can be easily plugged together to fulfill more complex requirements. For example the [ca-certificates](https://github.com/paketo-buildpacks/ca-certificates) enables adding additional certificates to the system trust-store at build and runtime. When using Cloud Native Buildpacks you can continuously benefit from the best practices coming from the community without any changes required.
+
+[Learn more about Cloud Native Buildpacks Concepts](https://buildpacks.io/docs/concepts/){ .learn-more}
+
+One way of using Cloud Native Buildpacks in CI/CD is by utilizing the [`cnbBuild`](https://www.project-piper.io/steps/cnbBuild/) step of Project "Piper". This does not require any special setup, like providing a Docker daemon, and works out of the box for Jenkins and Azure DevOps Pipelines.
+
+[Learn more about Support for Cloud Native Buildpacks in Jenkins](https://medium.com/buildpacks/support-for-cloud-native-buildpacks-in-jenkins-656330156e77){ .learn-more}
+
+
+
+### Get Access to a Cluster
+
+You can either purchase a Kyma cluster from SAP, create your [personal trial](https://hanatrial.ondemand.com/) account or sign-up for the [free tier](https://www.sap.com/products/business-technology-platform/trial.html#new-customers) offering to get a SAP managed Kyma Kubernetes cluster.
+
+
+
+### Get Access to a Container Registry
+
+SAP BTP doesn't provide a container registry.
+
+You can choose from offerings of hosted open source and private container image registries, as well as solutions that can be run on premise or in your own cloud infrastructure. However, you need to consider that the Kubernetes cluster needs to access the container registry from its network.
+
++ The use of a public container registry gives everyone access to your container images.
++ In a private container registry, your container images are protected. You will need to configure a **pull secret** to allow your cluster to access it.
+
+#### Setup Your Cluster for a Public Container Registry
+
+Make sure that the container registry is accessible from your Kubernetes cluster. No further setup is required.
+
+#### Setup Your Cluster for a Private Container Registry
+
+To use a docker image from a private repository, you need to [create an image pull secret](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) and configure this secret for your containers.
+::: warning
+It is recommended to use a technical user for this secret that has only read permission, because users with access to the Kubernetes cluster can reveal the password from the secret easily.
+:::
+
+
+
+
diff --git a/guides/domain-modeling.md b/guides/domain-modeling.md
index 7da10711d..62c4ba4e7 100644
--- a/guides/domain-modeling.md
+++ b/guides/domain-modeling.md
@@ -612,7 +612,7 @@ We can also apply named aspects as **includes** in an inheritance-like syntax:
entity Books : NamedAspect { ... }
```
-[Learn more about Aspects in the _CDS Language Reference_](../cds/cdl#aspects){ .learn-more}
+[Learn more about the usage of aspects in the _Aspect-oriented Modeling_ section](../cds/aspects).{ .learn-more}
::: tip
diff --git a/guides/extensibility/assets/cap-samples.drawio.svg b/guides/extensibility/assets/cap-samples.drawio.svg
new file mode 100644
index 000000000..9d9396e02
--- /dev/null
+++ b/guides/extensibility/assets/cap-samples.drawio.svg
@@ -0,0 +1,234 @@
+
\ No newline at end of file
diff --git a/guides/extensibility/assets/customization-old.md b/guides/extensibility/assets/customization-old.md
new file mode 100644
index 000000000..109434a65
--- /dev/null
+++ b/guides/extensibility/assets/customization-old.md
@@ -0,0 +1,249 @@
+---
+# layout: cookbook
+shorty: Extensibility (Old)
+title: Extending and Customizing SaaS Solutions (Old)
+synopsis: >
+ This guide explains how subscribers of SaaS applications can extend these on tenant level, thereby customizing them for their specific needs.
+breadcrumbs:
+ - Cookbook
+ - Extensibility
+ - SaaS Extensibility (Old)
+status: released
+search: false
+---
+
+
+# Extending and Customizing SaaS Solutions (Old)
+
+{{ $frontmatter.synopsis }}
+
+::: warning
+This guide assumes that the SaaS application is deployed using the _previous_ MTX services package `@sap/cds-mtx`. Projects working with the _new_ MTX version, see [the extensibility guide](../customization).
+
+Check if you can migrate to the _new_ package `@sap/cds-mtxs` with the help of our [migration guide](../../multitenancy/old-mtx-migration) and the [list of current limitations](https://github.tools.sap/cap/cds-tools/issues/317#issuecomment-1300295).
+:::
+
+
+
+## Extend SaaS Applications { #extend-saas-applications}
+
+Subscribers (customers) of a SaaS application can extend data and service models in the context of their subscription (= tenant context = subaccount context). New fields can be added to SAP-provided database tables. Those fields can be added to UIs as well, if they have been built with SAP's Fiori Elements technology.
+
+The overall process is depicted in the following figure:
+
+
+
+## Platform Setup
+
+#### Set Up Tenant Landscape
+
+Using SAP Business Technology Platform (BTP) cockpit, an account administrator sets up a "landscape of tenants" (= multiple subaccounts) to enable a staged extension development scenario (for example, _development_ and _productive_ tenants). We recommend setting up at least a development tenant to test extended data, service, and UI models before activating these into the productive tenant.
+
+#### Subscribe to SaaS Application
+
+Using SAP BTP cockpit, a subaccount administrator subscribes to the SaaS application. During this subscription, the SaaS application automatically performs a tenant onboarding step, which (if using SAP HANA) allocates an SAP HANA persistence for this tenant (= subaccount) and deploys all database objects.
+
+> Extending CDS services and database entities is only possible if the SaaS application is using the SAP HANA database service.
+> In addition, the SaaS application must have been enabled for extensibility by the SaaS provider.
+
+#### Authorize Extension Developer
+
+The extension is done by an extension developer (a customer role). The privilege to extend the base model of a tenant is linked to a scope of the SaaS application. Therefore, the security administrator of a subaccount has to grant this scope to the developer in SAP BTP cockpit. As a prerequisite, the developer has to be registered in the Identity Provider linked to the subaccount.
+
+There are two relevant scopes that can be assigned to extension developers:
+
+| Scope | |
+| -------------- | ----------------------------------------- |
+| ExtendCDS | Create extension projects and apply extension files. Not authorized to delete tables created by previous extensions |
+| ExtendCDSdelete | In addition, enables deletion of tables created by previous extensions, which can cause data loss! |
+
+The SaaS application delivers role templates including these scopes. For more information, see the documentation of the SaaS application.
+
+### Start Extension Project
+
+Extension developers initialize an extension project on the file system.
+
+Prerequisites:
+
++ The CDS command line tools must be installed. See [Getting Started > Setup](../../../get-started/#setup) for more details.
++ The Identity Provider linked to the tenant's subaccount must support the SAML standard.
++ We recommended using an Integrated Development Environment (IDE) with one of the available CDS editors for authoring extension CDS files.
++ Basic knowledge of the CDS language.
+
+> Use the regular `cds help` feature to learn more about command options.
+> For instance, to see a description of the command `cds extend`, use `cds help extend`.
+
+The CDS client communicates with the SaaS application and fetches the "base model" (the not-yet-extended model) from there. An extension project folder is generated on the local file system. If an extension has already happened before, the last activated extension is fetched as well.
+
+As an extension developer, initialize an extension project with the following command:
+
+```sh
+cds extend [-d ] [-s ] [-p ]
+```
+
+`` is specific to the SaaS application you're going to extend. This URL can be found in the documentation for the respective SaaS application. Usually, `` is the same URL visible on the subscriptions tab of SAP BTP cockpit, which is used to launch the application, enhanced with an additional URL path segment (for example, `/extend`). However, the SaaS application can decide to provide a different URL for working with extensions.
+
+`` is the folder on your local disk, which will contain the extension project files.
+If omitted, the current working directory (or an existing subdirectory named as the subdomain) will be used.
+
+`` in a productive landscape is automatically derived as the string preceding the first dot '.'.
+In other landscapes, find out the subdomain in the SAP BTP cockpit by navigating to the overview page for your tenant's subaccount. Then use the option `-s `.
+
+`` is a temporary authentication code, which is used to connect to the SaaS application. This passcode can be retrieved by opening a browser logon page. The URL of this browser page depends on SAP BTP landscape where the SaaS application is running, and the tenant that will be extended:
+
+```txt
+ = https://.authentication..hana.ondemand.com/passcode
+```
+
+A passcode can be used only once and within a limited period of time. When expired, a new passcode has to be generated and sent again.
+If you omit the passcode, it will be queried interactively.
+
+As a result of `cds extend`, an extension project is created in the specified folder. As an example, the following file/folder structure is generated on the local disk:
+
+```sh
+myextproject/
+ package.json # extension project descriptor
+ srv/
+ # will contain service and ui-related extension cds files
+ db/
+ # will contain db-related extension cds files
+ node_modules/
+ _base/
+ ... # contains the base model provided by the SaaS application
+```
+
+The `node_modules` folder should be hidden when using an IDE, because it contains artifacts (the base CDS model of the SaaS application) that can't be changed. SaaS applications can provide templates to document how to do meaningful extensions of base entities and services.
+
+This project structure follows the same conventions as introduced for developing entire CDS applications. Model extension files, which are relevant for a database deployment, must be placed in the `db/` folder. Service extension files must be placed in the `srv/` folder. The base model is treated like a reuse model. You can refer to it in extension files simply by `using ... from '_base/...'`
+
+> Extension developers should drive extension projects similar to other development projects. We recommend using a version control system (for example, Git) to host the extension project sources.
+
+### Save an Authentication Token for a Simplified Workflow
+
+As an extension developer, you have the option to work more smoothly by authenticating only once.
+This is achieved by using the command `cds login`, which will save authentication data to your local system.
+Saved authentication data can be deleted using `cds logout`.
+
+The command fetches tokens and corresponding refresh tokens and saves them for later use in either a plain-text file or on the desktop keyring
+(to be precise: libsecret on Linux, Keychain on macOS, or Credential Vault on Windows).
+
+Using the keyring has the advantage of increased security since, depending on the platform, you can lock and unlock it, and data saved by `cds login` can be inaccessible to other applications you run.
+
+`cds login` therefore uses the keyring by default. In order for this to succeed, you'll need to install an additional Node.js module, [_keytar_](https://www.npmjs.com/package/keytar):
+
+```sh
+npm i -g keytar
+```
+
+Alternatively, you can request `cds login` to write to a plain-text file.
+
+::: warning _β Warning_
+Local storage of authentication data incurs a security risk: a malicious, locally running application might be able to perform all actions that you're authorized for with the SaaS app in the context of your tenant.
+:::
+
+> In SAP Business Application Studio, plain-text storage is enforced since no desktop keyring is available. Don't worry - the plain-text file resides on an encrypted storage.
+
+Authentication data saved by `cds login` will be provided to `cds extend` and `cds activate` automatically, allowing you to call these commands later without providing a passcode.
+For convenience, `cds login` also saves further settings for the current project, so you don't have to provide it again (including the app URL).
+
+Once a saved token has expired, it will be automatically renewed using a refresh token, if such has been provided with the token.
+The validity of the original token and the refresh token can be configured by the SaaS-app provider. By default, the
+refresh token expires much later, allowing you to work without re-entering passcodes for multiple successive days.
+
+Should you later want to extend another SaaS application, you can log in to it as well, and it won't affect your first login.
+Logins are independent of each other, and both `cds extend` and `cds activate` will be authenticated according to the requested target.
+
+If you work with Cloud Foundry (CF), you can call `cds login` giving a passcode only. The command will then consult the Cloud Foundry command line client to determine suitable apps from the org and space currently logged into and will show you a list of apps and their respective URLs to choose from.
+
+To log in to the SaaS app with a passcode only, first change to the folder you want to use for your extension project. Then run:
+
+```sh
+cds login -p
+```
+
+Alternatively, if you don't work with CF or already have the app URL at hand, you can log in quicker with:
+
+```sh
+cds login -p
+```
+
+The tenant subdomain, unless explicitly given using `-s `, will also be determined using the CF client if available.
+
+To use a plain-text file instead of the keyring, append `--plain`. If you change your usage of this option, `cds login` will migrate any potentially pre-existing authentication data from the other storage to the requested storage.
+
+For a synopsis of all options, run `cds help login`.
+
+Once you've logged in to the SaaS app, you can omit the passcode, the app URL, and the tenant subdomain, so your development cycle might look like this:
+
+```sh
+cds extend
+# develop your extension
+cds activate
+# develop your extension
+cds activate
+# ...
+```
+
+To remove locally saved authentication data and optionally project settings, run:
+
+```sh
+cds logout
+```
+
+inside your extension-project folder.
+Again, `cds help logout` is available for more details.
+::: tip
+When your role-collection assignments have changed, run `cds logout` followed by `cds login` in order to fetch a token containing the new set of scopes.
+:::
+
+### Develop and Activate Extensions { #about-extension-models}
+
+Developing CDS model files is supported by the CDS editor and CDS build tools. Within these files, you can reference base model files with `using ... from '_base/...'` statements. Entities and services can be extended using the [cds `extend` technique](../../../cds/cdl#aspects). The following example shows how to add two fields to a `Books` database table of a hypothetical Bookshop application. An _extension.cds_ file is created (the file name doesn't matter) within the `db`-folder:
+
+```cds
+using sap.bookshop from '_base/db/datamodel';
+
+extend entity bookshop.Books with {
+ GTIN: String(14);
+ rating: Integer;
+}
+```
+
+The extension enhances the data model for this use case. Be aware that attributes of existing elements, such as type, key, and default cannot be modified.
+
+Extensions can be activated into a tenant using the following command:
+
+```sh
+cds activate []
+```
+
+If you omit the directory, the current directory, or a subdirectory named as the tenant subdomain, will be used.
+
+As outlined above, this command reuses a potentially saved authentication token for the target app URL and tenant subdomain.
+If you don't want to save authentication data locally, you can use the `-p ` option as well as `-s ` and `--to ` to (re-)connect.
+
+Activating an existing project into a different tenant requires setting `` and `` appropriately.
+
+Run `cds help activate` for more details.
+
+> By using `cds activate`, it isn't possible to upload csv-files into the extended tenant.
+
+
+
+#### Executing `cds extend` on an Existing Extension Project
+
+`cds extend` is used to create and initialize an extension project. Subsequent executions of `cds extend` must be done with the `--force` option to overwrite existing files (base model files and extension files). No files will be deleted. Features of a version control system should be used to detect and merge changes.
+
+#### Fetching Extension Templates from the SaaS Application
+
+SaaS applications can deliver template files, which demonstrate how to extend entities and services for this SaaS application. Such templates can be fetched from the server by using the `--templates` option of the `cds extend` command. As a result, a `tpl` folder will be generated containing the extension template files.
+
+
+
+### Deploy Extensions to Production
+
+The productive tenant (the productive subaccount on SAP BTP) is technically treated the same way as any development tenant. Role-based authorization can restrict the access to the productive tenant to dedicated extension developers. An extension project, which has been generated by referencing a development tenant, can be activated into a productive tenant by using `cds activate` with the appropriate options.
+
+## [Old MTX Reference](../../multitenancy/old-mtx-apis) {.toc-redirect}
+
+[See Reference docs for former, 'old' MTX Services.](../../multitenancy/old-mtx-apis)
diff --git a/guides/extensibility/assets/delegate-requests.drawio.svg b/guides/extensibility/assets/delegate-requests.drawio.svg
new file mode 100644
index 000000000..0effb146e
--- /dev/null
+++ b/guides/extensibility/assets/delegate-requests.drawio.svg
@@ -0,0 +1,127 @@
+
\ No newline at end of file
diff --git a/guides/extensibility/assets/extensibility.drawio.svg b/guides/extensibility/assets/extensibility.drawio.svg
new file mode 100644
index 000000000..567a3a87a
--- /dev/null
+++ b/guides/extensibility/assets/extensibility.drawio.svg
@@ -0,0 +1,246 @@
+
\ No newline at end of file
diff --git a/guides/extensibility/assets/feature-toggles.drawio.svg b/guides/extensibility/assets/feature-toggles.drawio.svg
new file mode 100644
index 000000000..9d58896a2
--- /dev/null
+++ b/guides/extensibility/assets/feature-toggles.drawio.svg
@@ -0,0 +1,208 @@
+
\ No newline at end of file
diff --git a/guides/extensibility/assets/image-20220628101642511.png b/guides/extensibility/assets/image-20220628101642511.png
new file mode 100644
index 000000000..363fb1a93
Binary files /dev/null and b/guides/extensibility/assets/image-20220628101642511.png differ
diff --git a/guides/extensibility/assets/image-20220630132726831.png b/guides/extensibility/assets/image-20220630132726831.png
new file mode 100644
index 000000000..fac071c08
Binary files /dev/null and b/guides/extensibility/assets/image-20220630132726831.png differ
diff --git a/guides/extensibility/assets/image-20221004054556898.png b/guides/extensibility/assets/image-20221004054556898.png
new file mode 100644
index 000000000..234b48bde
Binary files /dev/null and b/guides/extensibility/assets/image-20221004054556898.png differ
diff --git a/guides/extensibility/assets/image-20221004080722532.png b/guides/extensibility/assets/image-20221004080722532.png
new file mode 100644
index 000000000..32e424aae
Binary files /dev/null and b/guides/extensibility/assets/image-20221004080722532.png differ
diff --git a/guides/extensibility/assets/image-20221004081826167.png b/guides/extensibility/assets/image-20221004081826167.png
new file mode 100644
index 000000000..93eb78b13
Binary files /dev/null and b/guides/extensibility/assets/image-20221004081826167.png differ
diff --git a/guides/extensibility/assets/java-deployment.png b/guides/extensibility/assets/java-deployment.png
new file mode 100644
index 000000000..23041afcf
Binary files /dev/null and b/guides/extensibility/assets/java-deployment.png differ
diff --git a/guides/extensibility/assets/node-deployment.png b/guides/extensibility/assets/node-deployment.png
new file mode 100644
index 000000000..0dac133bf
Binary files /dev/null and b/guides/extensibility/assets/node-deployment.png differ
diff --git a/guides/extensibility/assets/orders-ext.png b/guides/extensibility/assets/orders-ext.png
new file mode 100644
index 000000000..eb382f6f0
Binary files /dev/null and b/guides/extensibility/assets/orders-ext.png differ
diff --git a/guides/extensibility/assets/process_SAP_BTP.drawio.svg b/guides/extensibility/assets/process_SAP_BTP.drawio.svg
new file mode 100644
index 000000000..1ca53e0e6
--- /dev/null
+++ b/guides/extensibility/assets/process_SAP_BTP.drawio.svg
@@ -0,0 +1,251 @@
+
\ No newline at end of file
diff --git a/guides/extensibility/assets/reuse-overview.drawio.svg b/guides/extensibility/assets/reuse-overview.drawio.svg
new file mode 100644
index 000000000..e00ffe289
--- /dev/null
+++ b/guides/extensibility/assets/reuse-overview.drawio.svg
@@ -0,0 +1,252 @@
+
\ No newline at end of file
diff --git a/guides/extensibility/assets/scenarios.drawio.svg b/guides/extensibility/assets/scenarios.drawio.svg
new file mode 100644
index 000000000..28f4ef8b1
--- /dev/null
+++ b/guides/extensibility/assets/scenarios.drawio.svg
@@ -0,0 +1,343 @@
+
\ No newline at end of file
diff --git a/guides/extensibility/assets/subdomain-cockpit-sui.png b/guides/extensibility/assets/subdomain-cockpit-sui.png
new file mode 100644
index 000000000..5b366a608
Binary files /dev/null and b/guides/extensibility/assets/subdomain-cockpit-sui.png differ
diff --git a/guides/extensibility/composition.md b/guides/extensibility/composition.md
new file mode 100644
index 000000000..586499ea1
--- /dev/null
+++ b/guides/extensibility/composition.md
@@ -0,0 +1,786 @@
+---
+shorty: Reuse & Compose
+synopsis: >
+ Learn how to compose enhanced, verticalized solutions by reusing content from other projects, and adapt them to your needs by adding extensions or projections.
+breadcrumbs:
+ - Cookbook
+ - Extensibility
+ - Composition
+status: released
+---
+
+# Reuse and Compose
+
+{{$frontmatter?.synopsis}}
+
+## Introduction and Overview
+
+CAP promotes reuse and composition by importing content from reuse packages. Reused content, shared and imported that way, can comprise models, code, initial data, and i18n bundles.
+
+
+### Usage Scenarios
+
+By applying CAP's techniques for reuse, composition, and integration, you can address several different usage scenarios, as depicted in the following illustration.
+
+
+
+
+
+1. **Verticalized/Composite Solutions** β Pick one or more reuse packages/services. Enhance them, mash them up into a composite solution, and offer this as a new packaged solution to clients.
+
+2. **Prebuilt Extension Packages** β Instead of offering a new packaged solution, you could also just provide your enhancements as a prebuilt extension package, for example, for **verticalization**, which you in turn offer to others as a reuse package.
+
+3. **Prebuilt Integration Packages** β Prebuilt extension packages could also involve prefabricated integrations to services in back-end systems, such as S/4HANA and SAP SuccessFactors.
+
+4. **Prebuilt Business Data Packages** β A variant of prebuilt integration packages, in which you would provide a reuse package that provides initial data for certain entities, like a list of *Languages*, *Countries*, *Regions*, *Currencies*, etc.
+
+5. **Customizing SaaS Solutions** β Customers, who are subscribers of SaaS solutions, can apply the same techniques to adapt SaaS solutions to their needs. They can use prebuilt extension or business data packages, or create their own custom-defined ones.
+
+
+### Examples from [cap/samples](https://github.com/sap-samples/cloud-cap-samples)
+
+In the following sections, we frequently refer to examples from [cap/samples](https://github.com/sap-samples/cloud-cap-samples):
+
+
+
+
+- **[@capire/bookshop](https://github.com/sap-samples/cloud-cap-samples/tree/main/bookshop)** provides a basic bookshop app and **reuse services** .
+- **[@capire/common](https://github.com/sap-samples/cloud-cap-samples/tree/main/common)** is a **prebuilt extension** and **business data** package for *Countries*, *Currencies*, and *Languages*.
+- **[@capire/reviews](https://github.com/sap-samples/cloud-cap-samples/tree/main/reviews)** provides an independent **reuse service**.
+- **[@capire/orders](https://github.com/sap-samples/cloud-cap-samples/tree/main/orders)** provides another independent **reuse service**.
+- **[@capire/bookstore](https://github.com/sap-samples/cloud-cap-samples/tree/main/bookstore)** combines all of the above into a **composite application**.
+
+
+### Preparation for Exercises
+
+If you want to exercise the code snippets in following sections, do the following:
+
+**1)** Β Get cap/samples:
+
+```sh
+git clone https://github.com/sap-samples/cloud-cap-samples samples
+cd samples
+npm install
+```
+
+**2)** Β Start a sample project:
+
+```sh
+cds init sample
+cd sample
+npm i
+# ... run the upcoming commands in here
+```
+
+## Importing Reuse Packages { #import}
+
+CAP and CDS promote reuse of prebuilt content based on `npm` or `Maven` techniques. The following figure shows the basic procedure for `npm`.
+
+
+
+> We use `npm` and `Maven` as package managers simply because we didn't want to reinvent the wheel here.
+
+### Using `npm add/install` from _npm_ Registries
+
+Use _`npm add/install`_ to import reuse packages to your project, like so:
+
+```sh
+npm add @capire/bookshop @capire/common
+```
+
+
+This installs the content of these packages into your project's `node_modules` folder and adds corresponding dependencies:
+
+::: code-group
+```json [package.json]
+{
+ "name": "sample", "version": "1.0.0",
+ "dependencies": {
+ "@capire/bookshop": "^1.0.0",
+ "@capire/common": "^1.0.0",
+ ...
+ }
+}
+```
+:::
+
+> These dependencies allow you to use `npm outdated`, `npm update`, and `npm install` later to get the latest versions of imported packages.
+
+
+### Importing from Other Sources
+
+In addition to importing from _npm_ registries, you can also import from local sources. This can be other CAP projects that you have access to, or tarballs of reuse packages, for example, downloaded from some marketplace.
+
+```sh
+npm add ~/Downloads/@capire-bookshop-1.0.0.tgz
+npm add ../bookshop
+```
+
+> You can use `npm pack` to create tarballs from your projects if you want to share them with others.
+
+
+### Importing from Maven Dependencies
+
+Add the dependency to the reuse package to your `pom.xml`:
+
+::: code-group
+```xml [pom.xml]
+
+ com.sap.capire
+ bookshop
+ 1.0.0
+
+```
+:::
+
+As Maven dependencies are - in contrast to `npm` packages - downloaded into a global cache, you need to make the artifacts from the reuse package available in your project locally.
+The CDS Maven Plugin provides a simple goal named `resolve`, that performs this task for you and extracts reuse packages into the `target/cds/` folder of the Maven project.
+Include this goal into the `pom.xml`, if not already present:
+
+::: code-group
+```xml [pom.xml]
+
+ com.sap.cds
+ cds-maven-plugin
+ ${cds.services.version}
+
+ ...
+
+ cds.resolve
+
+ resolve
+
+
+ ...
+
+
+```
+:::
+
+### Embedding vs. Integrating Reuse Services { #embedding-vs-integration}
+
+By default, when importing reuse packages, all imported content becomes an integral part of your project, it literally becomes **embedded** in your project. This applies to all the things an imported package can contain, such as:
+
+- Domain models
+- Service definitions
+- Service implementations
+- i18n bundles
+- Initial data
+
+[See an example for a data package for `@sap/cds/common`](../../cds/common#prebuilt-data){ .learn-more}
+
+However, you decide which parts to actually use and activate in your project by means of model references as shown in the following sections.
+
+Instead of embedding reuse content, you can also **integrate** with remote services, deployed as separate microservices as outlined in [*Service Integration*](#service-integration).
+
+
+
+## Reuse & Extend Models {#reuse-models}
+
+Even though all imported content is embedded in your project, you decide which parts to actually use and activate by means of model references. For example, if an imported package comes with three service definitions, it's still you who decides which of them to serve as part of your app, if any. The rule is:
+
+::: tip Active by Reachability
+Everything that you are referring to from your own models is served.
+Everything outside of your models is ignored.
+:::
+
+### Via `using from` Directives
+
+Use the definitions from imported models through [`using` directives](../../cds/cdl#model-imports) as usual. For example, like in [@capire/bookstore](https://github.com/SAP-samples/cloud-cap-samples/blob/7b7686cb29aa835e17a95829c56dc3285e6e23b5/bookstore/srv/mashup.cds), simply add all:
+
+::: code-group
+```cds [bookstore/srv/mashup.cds]
+using from '@capire/bookshop';
+using from '@capire/common';
+```
+:::
+
+The `cds` compiler finds the imported content in `node_modules` when processing imports with absolute targets as shown previously.
+
+### Using _index.cds_ Entry Points {#index-cds}
+
+The above `using from` statements assume that the imported packages provide _index.cds_ in their roots as [public entry points](#entry-points), which they do. For example see [@capire/bookshop/index.cds](https://github.com/SAP-samples/cloud-cap-samples/blob/7b7686cb29aa835e17a95829c56dc3285e6e23b5/bookshop/index.cds):
+
+::: code-group
+```cds [bookshop/index.cds]
+// exposing everything...
+using from './db/schema';
+using from './srv/cat-service';
+using from './srv/admin-service';
+```
+:::
+
+This _index.cds_ imports and therefore activates everything. Running `cds watch` in your project would show you this log output, indicating that all initial data and services from your imported packages are now embedded and served from your app:
+
+```log
+[cds] - connect to db > sqlite { database: ':memory:' }
+ > filling sap.common.Currencies from common/data/sap.common-Currencies.csv
+ > filling sap.common.Currencies_texts from common/data/sap.common-Currencies_texts.csv
+ > filling sap.capire.bookshop.Authors from bookshop/db/data/sap.capire.bookshop-Authors.csv
+ > filling sap.capire.bookshop.Books from bookshop/db/data/sap.capire.bookshop-Books.csv
+ > filling sap.capire.bookshop.Books_texts from bookshop/db/data/sap.capire.bookshop-Books_texts.csv
+ > filling sap.capire.bookshop.Genres from bookshop/db/data/sap.capire.bookshop-Genres.csv
+/> successfully deployed to sqlite in-memory db
+
+[cds] - serving AdminService { at: '/admin', impl: 'bookshop/srv/admin-service.js' }
+[cds] - serving CatalogService { at: '/browse', impl: 'bookshop/srv/cat-service.js' }
+```
+
+
+
+### Using Different Entry Points
+
+If you don't want everything, but only a part, you can change your `using from` directives like this:
+
+```cds
+using { CatalogService } from '@capire/bookshop/srv/cat-service';
+```
+
+ The output of `cds watch` would reduce to:
+
+```log
+[cds] - connect to db > sqlite { database: ':memory:' }
+ > filling sap.capire.bookshop.Authors from bookshop/db/data/sap.capire.bookshop-Authors.csv
+ > filling sap.capire.bookshop.Books from bookshop/db/data/sap.capire.bookshop-Books.csv
+ > filling sap.capire.bookshop.Books_texts from bookshop/db/data/sap.capire.bookshop-Books_texts.csv
+ > filling sap.capire.bookshop.Genres from bookshop/db/data/sap.capire.bookshop-Genres.csv
+/> successfully deployed to sqlite in-memory db
+
+[cds] - serving CatalogService { at: '/browse', impl: 'bookshop/srv/cat-service.js' }
+```
+
+
+> Only the CatalogService is served now.
+::: tip
+Check the _readme_ files that come with reuse packages for information about which entry points are safe to use.
+:::
+
+
+### Extending Imported Definitions
+
+You can freely use all definitions from the imported models in the same way as you use definitions from your own models. This includes using declared types, adding associations to imported entities, building views on top of imported entities, and so on.
+
+You can even extend imported definitions, for example, add elements to imported entities, or add/override annotations, without limitations.
+
+Here's an example from the [@capire/bookstore](https://github.com/SAP-samples/cloud-cap-samples/blob/7b7686cb29aa835e17a95829c56dc3285e6e23b5/bookstore/srv/mashup.cds):
+
+::: code-group
+```cds [bookstore/srv/mashup.cds]
+using { sap.capire.bookshop.Books } from '@capire/bookshop';
+using { ReviewsService.Reviews } from '@capire/reviews';
+
+// Extend Books with access to Reviews and average ratings
+extend Books with {
+ reviews : Composition of many Reviews on reviews.subject = $self.ID;
+ rating : Decimal;
+}
+```
+:::
+
+
+## Reuse & Extend Code {#reuse-code}
+
+
+Service implementations, in particular custom-coding, are also imported and served in embedding projects. Follow the instructions if you need to add additional custom handlers.
+
+### In Node.js
+
+One way to add your own implementations is to replace the service implementation as follows:
+
+1. Add/override the `@impl` annotation
+ ```cds
+ using { CatalogService } from '@capire/bookshop';
+ annotate CatalogService with @impl:'srv/my-cat-service-impl';
+ ```
+
+2. Place your implementation in `srv/my-cat-service-impl.js`:
+ ::: code-group
+ ```js [srv/my-cat-service-impl.js]
+ module.exports = cds.service.impl (function(){
+ this.on (...) // add your event handlers
+ })
+ ```
+ :::
+
+3. If the imported package already had a custom implementation, you can include that as follows:
+ ::: code-group
+ ```js [srv/my-cat-service-impl.js]
+ const base_impl = require ('@capire/bookshop/srv/cat-service')
+ module.exports = cds.service.impl (async function(){
+ this.on (...) // add your event handlers
+ await base_impl.call (this,this)
+ })
+ ```
+ :::
+ > Make sure to invoke the base implementation exactly like that, with `await`. And make sure to check the imported package's readme to check whether access to that implementation module is safe.
+
+
+### In Java
+
+You can provide your own implementation in the same way, as you do for your own services:
+
+1. Import the service in your CDS files
+ ```cds
+ using { CatalogService } from 'com.sap.capire/bookshop';
+ ```
+
+2. Add your own implementation next to your other event handler classes:
+ ```java
+ @Component
+ @ServiceName("CatalogService")
+ public class CatalogServiceHandler implements EventHandler {
+
+ @On(/* ... */)
+ void myHandler(EventContext context) {
+ // ...
+ }
+
+ }
+ ```
+
+## Reuse & Extend UIs {#reuse-uis}
+
+If imported packages provide UIs, you can also serve them as part of your app β for example, using standard [express.js](https://expressjs.com) middleware means in Node.js.
+
+The *@capire/bookstore* app has this [in its `server.js`](https://github.com/SAP-samples/cloud-cap-samples/blob/7b7686cb29aa835e17a95829c56dc3285e6e23b5/bookstore/server.js) to serve [the Vue.js app imported with *@capire/bookshop*](https://github.com/SAP-samples/cloud-cap-samples/tree/7b7686cb29aa835e17a95829c56dc3285e6e23b5/bookshop/app/vue) using the `app.serve().from()` method:
+
+::: code-group
+```js [bookstore/server.js]
+const express = require('express')
+const cds = require('@sap/cds')
+
+// Add routes to UIs from imported packages
+cds.once('bootstrap',(app)=>{
+ app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
+ app.serve ('/reviews') .from ('@capire/reviews','app/vue')
+ app.serve ('/orders') .from('@capire/orders','app/orders')
+})
+```
+:::
+
+[More about Vue.js in our _Getting Started in a Nutshell_](../../get-started/in-a-nutshell#uis){.learn-more} [Learn more about serving Fiori UIs.](../../advanced/fiori){.learn-more}
+
+This ensures all static content for the app is served from the imported package.
+
+In both cases, all dynamic requests to the service endpoint anyways reach the embedded service, which is automatically served at the same endpoint it was served in the bookshop.
+
+In case of Fiori elements-based UIs, the reused UIs can be extended by [extending their models as decribed above](#reuse-models), in this case overriding or adding Fiori annotations.
+
+
+
+## Service Integration
+
+Instead of embedding and serving imported services as part of your application, you can decide to integrate with them, having them deployed and run as separate microservices.
+
+
+
+
+### Import the Remote Service's APIs
+
+This is described in the [Import Reuse Packages section](#import) →Β for example using `npm add`.
+
+Here's the effect of this step in [@capire/bookstore](https://github.com/SAP-samples/cloud-cap-samples/blob/7b7686cb29aa835e17a95829c56dc3285e6e23b5/bookstore/package.json):
+
+::: code-group
+```json [bookstore/package.json]
+ "dependencies": {
+ "@capire/bookshop": "^1.0.0",
+ "@capire/reviews": "^1.0.0",
+ "@capire/orders": "^1.0.0",
+ "@capire/common": "^1.0.0",
+ ...
+ },
+```
+:::
+
+### Configuring Required Services
+
+To configure required remote services in Node.js, simply add the respective entries to the [`cds.requires` config option](../../node.js/cds-env). You can see an example in [@capire/bookstore/package.json](https://github.com/SAP-samples/cloud-cap-samples/blob/7b7686cb29aa835e17a95829c56dc3285e6e23b5/bookstore/package.json), which integrates [@capire/reviews](https://github.com/SAP-samples/cloud-cap-samples/tree/7b7686cb29aa835e17a95829c56dc3285e6e23b5/reviews) and [@capire/orders](https://github.com/SAP-samples/cloud-cap-samples/tree/7b7686cb29aa835e17a95829c56dc3285e6e23b5/orders) as remote service:
+
+::: code-group
+```json [bookstore/package.json]
+"cds": {
+ "requires": {
+ "ReviewsService": {
+ "kind": "odata", "model": "@capire/reviews"
+ },
+ "OrdersService": {
+ "kind": "odata", "model": "@capire/orders"
+ },
+ }
+}
+```
+:::
+
+> Essentially, this tells the service loader to not serve that service as part of your application, but expects a service binding at runtime in order to connect to the external service provider.
+
+#### Restricted Reuse Options
+
+Because models of integrated services only serve as imported APIs, you're restricted with respect to how you can use the models of services to integrate with. For example, only adding fields is possible, or cross-service navigation and expands.
+
+Yet, there are options to make some of these work programmatically. This is explained in the [next section](#delegating-calls) based on the integration of [@capire/reviews](https://github.com/SAP-samples/cloud-cap-samples/tree/7b7686cb29aa835e17a95829c56dc3285e6e23b5/reviews) in [@capire/bookstore](https://github.com/SAP-samples/cloud-cap-samples/tree/7b7686cb29aa835e17a95829c56dc3285e6e23b5/bookstore).
+
+
+
+### Delegating Calls to Remote Services { #delegating-calls}
+
+Let's start from the following use case: The bookshop app exposed through [@capire/bookstore](https://github.com/SAP-samples/cloud-cap-samples/tree/7b7686cb29aa835e17a95829c56dc3285e6e23b5/bookstore) will allow end users to see the top 10 book reviews in the details page.
+
+To avoid [CORS issues](https://developer.mozilla.org/de/docs/Web/HTTP/CORS), the request from the UI goes to the main `CatalogService` serving the end user's UI and is delegated from that to the remote `ReviewsService`, as shown in this sequence diagram:
+
+ 
+
+And this is how we do that in [@cap/bookstore](https://github.com/SAP-samples/cloud-cap-samples/blob/7b7686cb29aa835e17a95829c56dc3285e6e23b5/bookstore/srv/mashup.js):
+
+::: code-group
+```js [bookstore/srv/mashup.js]
+const CatalogService = await cds.connect.to ('CatalogService')
+const ReviewsService = await cds.connect.to ('ReviewsService')
+CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => {
+ console.debug ('> delegating request to ReviewsService')
+ const [id] = req.params, { columns, limit } = req.query.SELECT
+ return ReviewsService.tx(req).read ('Reviews',columns).limit(limit).where({subject:String(id)})
+}))
+```
+:::
+
+Let's look at that step by step:
+
+1. We connect to both the `CatalogService` (local) and the `ReviewsService` (remote) to mash them up.
+2. We register an `.on` handler with the `CatalogService`, which delegates the incoming request to the `ReviewsService`.
+3. We wrap that into a call to `.prepend` because the `.on` handler needs to supersede the default generic handlers provided by the CAP runtime β see [ref docs for `srv.prepend`.](../../node.js/core-services#srv-prepend)
+
+
+
+### Running with Mocked Remote Services {#mocking-required-services}
+
+If you start [@capire/bookstore](https://github.com/SAP-samples/cloud-cap-samples/tree/7b7686cb29aa835e17a95829c56dc3285e6e23b5/bookstore) locally with `cds watch`, all [required services](https://github.com/SAP-samples/cloud-cap-samples/blob/7b7686cb29aa835e17a95829c56dc3285e6e23b5/bookstore/package.json#L15-L22) are automatically mocked, as you can see in the log output when the server starts:
+
+```log
+[cds] - serving AdminService { at: '/admin', impl: 'bookshop/srv/admin-service.js' }
+[cds] - serving CatalogService { at: '/browse', impl: 'bookshop/srv/cat-service.js' }
+[cds] - mocking OrdersService { at: '/orders', impl: 'orders/srv/orders-service.js' }
+[cds] - mocking ReviewsService { at: '/reviews', impl: 'reviews/srv/reviews-service.js' }
+```
+
+> →Β `OrdersService` and `ReviewsService` are mocked, that is, served in the same process, in the same way as the local services.
+
+This allows development and testing functionality with minimum complexity and overhead in fast, closed-loop dev cycles.
+
+
+As all services are co-located in the same process, sharing the same database, you can send requests like this, which join/expand across *Books* and *Reviews*:
+```http
+GET http://localhost:4004/browse/Books/201?
+&$expand=reviews
+&$select=ID,title,rating
+```
+
+
+### Testing Remote Integration Locally {#testing-locally}
+
+As a next step, following CAP's [Grow-as-you-go](../../about/#grow-as-you-go) philosophy, we can run the services as separate processes to test the remote integration, but still locally in a low-complexity setup. We use the [_automatic binding by `cds watch`_](#bindings-via-cds-watch) as follows:
+
+1. Start the three servers separately, each in a separate shell (from within the root folder in your cloned _[cap/samples]( https://github.com/sap-samples/cloud-cap-samples)_ project):
+ ```sh
+ cds watch orders --port 4006
+ ```
+ ```sh
+ cds watch reviews --port 4005
+ ```
+ ```sh
+ cds watch bookstore --port 4004
+ ```
+
+2. Send a few requests to the reviews service (port 4005) to add `Reviews`:
+ ```http
+ POST http://localhost:4005/Reviews
+ Content-Type: application/json;IEEE754Compatible=true
+ Authorization: Basic itsme:secret
+ {"subject":"201", "title":"boo", "rating":3 }
+ ```
+
+3. Send a request to bookshop (port 4004) to fetch reviews via `CatalogService`:
+ ```http
+ GET http://localhost:4004/browse/Books/201/reviews?
+ &$select=rating,date,title
+ &$top=3
+ ```
+
+ > You can find a script for this in [@capire/bookstore/test/requests.http](https://github.com/SAP-samples/cloud-cap-samples/blob/7b7686cb29aa835e17a95829c56dc3285e6e23b5/bookstore/test/requests.http).
+
+
+
+
+### Binding Required Services
+
+Service bindings provide the details about how to reach a required service at runtime, that is, providing the necessary credentials, most prominently the target service's `url`.
+
+
+
+#### Basic Mechanism Using `cds.env` and Process env Variables {#bindings-via-cds-env}
+
+
+At the end of the day, the CAP Node.js runtime expects to find the service bindings in the respective entries in `cds.env.requires`:
+
+1. Configured required services constitute endpoints for service bindings:
+
+ ::: code-group
+ ```json [package.json]
+ "cds": {
+ "requires": {
+ "ReviewsService": {...},
+ }
+ }
+ ```
+ :::
+
+2. These are made available to the runtime via `cds.env.requires`.
+
+ ```js
+ const { ReviewsService } = cds.env.requires
+ ```
+
+3. Service bindings essentially fill in `credentials` to these entries.
+
+ ```js
+ const { ReviewsService } = cds.env.requires
+ //> ReviewsService.credentials = {
+ //> url: "http://localhost:4005/reviews"
+ //> }
+ ```
+
+While you could do the latter in test suites, you would never provide credentials in a hard-coded way like that in productive code. Instead, you'd use one of the options presented in the following sections.
+
+
+
+#### Automatic Bindings by `cds watch` {#bindings-via-cds-watch}
+
+When running separate services locally as described [in the previous section](#testing-locally), this is done automatically by `cds watch`, as indicated by this line in the bootstrapping log output:
+
+```log
+[cds] - using bindings from: { registry: '~/.cds-services.json' }
+```
+
+You can cmd/ctrl-click or double click on that to see the file's content, and find something like this:
+
+::: code-group
+```json [~/.cds-services.json]
+{
+ "cds": {
+ "provides": {
+ "OrdersService": {
+ "kind": "odata",
+ "credentials": {
+ "url": "http://localhost:4006/orders"
+ }
+ },
+ "ReviewsService": {
+ "kind": "odata",
+ "credentials": {
+ "url": "http://localhost:4005/reviews"
+ }
+ },
+ "AdminService": {
+ "kind": "odata",
+ "credentials": {
+ "url": "http://localhost:4004/admin"
+ }
+ },
+ "CatalogService": {
+ "kind": "odata",
+ "credentials": {
+ "url": "http://localhost:4004/browse"
+ }
+ }
+ }
+ }
+}
+```
+:::
+
+Whenever you start a CAP server with `cds watch`, this is what happens automatically:
+
+1. For all *provided* services, corresponding entries are written to _~/cds-services.json_ with respective `credentials`, namely the `url`.
+
+2. For all *required* services, corresponding entries are fetched from _~/cds-services.json_. If found, the `credentials` are filled into the respective entry in `cds.env.requires.` [as introduced previously](#bindings-via-cds-env).
+
+In effect, all the services that you start locally in separate processes automatically receive their required bindings so they can talk to each other out of the box.
+
+
+
+#### Through Process Environment Variables {#bindings-via-process-env}
+
+You can pass credentials as process environment variables, for example in ad-hoc tests from the command line:
+
+```sh
+export cds_requires_ReviewsService_credentials_url=http://localhost:4005/reviews
+cds watch bookstore
+```
+
+... or add them to a local `.env` file for repeated local tests:
+
+::: code-group
+```properties [.env]
+cds.requires.ReviewsService.credentials = { "url": "http://localhost:4005/reviews" }
+```
+:::
+> Note: never check in or deploy these `.env` files!
+
+
+
+#### Through `VCAP_SERVICES` {#bindings-via-vcap_services}
+
+When deploying to Cloud Foundry, service bindings are provided in `VCAP_SERVICES` process environment variables [as documented here](../../node.js/cds-connect#vcap_services).
+
+
+
+#### In Target Cloud Environments {#bindings-in-cloud-environments}
+
+Find information about how to do so in different environment under these links:
+
+- [Deploying Services using MTA Deployer](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c2b99f19e9264c4d9ae9221b22f6f589/33548a721e6548688605049792d55295.html)
+- [Service Bindings in SAP BTP Cockpit](https://help.sap.com/docs/SERVICEMANAGEMENT/09cc82baadc542a688176dce601398de/0e6850de6e7146c3a17b86736e80ee2e.html)
+- [Service Bindings using the Cloud Foundry CLI](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/296cd5945fd84d7d91061b2b2bcacb93.html)
+- [Service Binding in Kyma](hhttps://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/d1aa23c492694d669c89a8d214f29147.html)
+
+
+
+
+
+
+## Providing Reuse Packages
+
+In general, every CAP-based product can serve as a reuse package consumed by others. There's actually not much to do. Just create models and implementations as usual. The following sections are about additional things to consider as a provider of a reuse package.
+
+### Considerations for Maven-based reuse packages
+
+When providing your reuse package as a Maven dependency you need to ensure that the CDS, CSV and i18n files are included into the JAR.
+Place them in a `cds` folder in your `resources` folder under a unique module directory (for example, leveraging group ID and artifact ID):
+
+```txt
+src/main/resources/cds/
+ com.sap.capire/bookshop/
+ index.cds
+ CatalogService.cds
+ data/
+ com.sap.capire.bookshop-Books.csv
+ i18n/
+ i18n.properties
+```
+
+This structure ensures that the CDS Maven Plugin `resolve` goal extracts these files correctly to the `target/cds/` folder.
+
+>Note that `com.sap.capire/bookshop` is used when importing the models with a `using` directive.
+
+### Provide Public Entry Points {#entry-points}
+
+Following the Node.js approach, there's no public/private mechanism in CDS.
+Instead, it's good and proven practice to add an _index.cds_ in the root folder of reuse packages, similar to the use of _index.js_ files in Node.
+
+For example:
+
+::: code-group
+```cds [provider/index.cds]
+namespace my.reuse.package;
+using from './db/schema';
+using from './srv/cat-service';
+using from './srv/admin-service';
+```
+:::
+
+This allows your users to refer to your models in `using` directives using just the package name, like so:
+
+::: code-group
+```cds [consumer/some.cds]
+using { my.thing } from 'my-reuse-package';
+```
+:::
+
+In addition, you might want to provide other entry points to ease partial usage options. For example, you could provide a _schema.cds_ file in your root, to allow using the domain model without services:
+
+::: code-group
+```cds [consumer/more.cds]
+using { my.domain.entity } from 'my-reuse-package/schema';
+using { my.service } from 'my-reuse-package/services';
+```
+:::
+
+### Provide Custom Handlers
+
+#### In Node.js
+
+In general, custom handlers can be placed in files matching the naming of the _.cds_ files they belong to. In a reuse package, you have to use the `@impl` annotation to make it explicit which custom handler to use. In addition you need to use the fully qualified module path inside the `@impl` annotation.
+
+Imagine that our bookshop is an _@sap_-scoped reuse module and the _CatalogService_ has a custom handler. This is how the service definition would look:
+
+::: code-group
+```cds [bookshop/srv/cat-service.cds]
+service CatalogService @(impl: '@sap/bookshop/srv/cat-service.js') {...}
+```
+:::
+
+#### In Java
+
+If your reuse project is Spring Boot independent, register your custom event handler classes in a `CdsRuntimeConfiguration`:
+
+::: code-group
+```java [src/main/java/com/sap/capire/bookshop/BookshopConfiguration.java]
+package com.sap.capire.bookshop;
+
+public class BookshopConfiguration implements CdsRuntimeConfiguration {
+
+ @Override
+ public void eventHandlers(CdsRuntimeConfigurer configurer) {
+ configurer.eventHandler(new CatalogServiceHandler());
+ }
+}
+```
+:::
+
+Additionally, register the `CdsRuntimeConfiguration` class in a `src/main/resources/META-INF/services/com.sap.cds.services.runtime.CdsRuntimeConfiguration` file to be detected by CAP Java:
+
+::: code-group
+``` txt [src/main/resources/META-INF/services/com.sap.cds.services.runtime.CdsRuntimeConfiguration]
+com.sap.capire.bookshop.BookshopConfiguration
+```
+:::
+
+Alternatively, if your reuse project is Spring Boot-based, define your event handler classes as Spring beans.
+Then use Spring Boot's [auto-configuration mechanism](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration) to ensure that your classes are registered automatically when importing the reuse package as a dependency.
+
+### Add a Readme
+
+You should inform potential consumers about the recommended ways to reuse content provided by your package. At least provide information about:
+
+- What is provided β schemas, services, data, and so on
+- What are the recommended, stable entry points
+
+
+
+### Publish/Share with Consumers
+
+The preferred way to share reuse packages is by publishing to registries, like _npmjs.org_, _pkg.github.com_ or _Maven Central_. This allows consumers to apply proper version management.
+
+However, at the end of the day, any other way to share packages, which you create with `npm pack` or `mvn package` would work as well.
+
+
+
+## Customizing SaaS Usage
+
+
+Subscribers of SaaS solutions can use the same *reuse and extend* techniques to tailor the application to their requirements, for example by:
+
+- Adding/overriding annotations
+- Adding custom fields and entities
+- Adding custom data
+- Adding custom i18n bundles
+- Importing prebuilt extension packages
+
+The main difference is how and from where the import happens:
+
+1. The reuse package, in this case, is the subscribed SaaS application.
+2. The import happens via `cds pull`.
+3. The imported package is named according to the `cds.extends` entry in package.json
+4. The extensions are applied via `cds push`.
+
+[Learn more in the **SaaS Extensibility** guide.](customization){.learn-more}
diff --git a/guides/extensibility/customization.md b/guides/extensibility/customization.md
new file mode 100644
index 000000000..16f87ced3
--- /dev/null
+++ b/guides/extensibility/customization.md
@@ -0,0 +1,1076 @@
+---
+# layout: cookbook
+shorty: Extend SaaS Apps
+synopsis: >
+ This guide explains how subscribers of SaaS applications can extend these on tenant level, thereby customizing them for their specific needs.
+breadcrumbs:
+ - Cookbook
+ - Extensibility
+ - Extending SaaS Apps
+#notebook: true
+status: released
+---
+
+# Extending SaaS Applications
+
+[[toc]]
+
+## Introduction & Overview
+
+Subscribers (customers) of SaaS solutions frequently need to tailor these to their specific needs, for example, by adding specific extension fields and entities. All CAP-based applications intrinsically support such **SaaS extensions** out of the box.
+
+The overall process is depicted in the following figure:
+
+
+
+In this guide, you will learn the following:
+
+- How to enable extensibility as a **SaaS provider**.
+- How to develop SaaS extensions as a **SaaS customer**.
+
+
+## Prerequisites {#prerequisites}
+
+Before we start, you'll need a **CAP-based [multitenant SaaS application](../multitenancy/)** that you can modify and deploy.
+
+
+::: tip Jumpstart
+You can download the ready-to-use [Orders Management application](https://github.com/SAP-samples/cloud-cap-samples/tree/main/orders):
+
+```sh
+git clone https://github.com/SAP-samples/cloud-cap-samples
+cd cloud-cap-samples/orders
+cds add multitenancy
+```
+
+Also, ensure you have the latest version of `@sap/cds-dk` installed globally:
+
+```sh
+npm update -g @sap/cds-dk
+```
+
+:::
+
+## As a SaaS Provider { #prep-as-provider }
+
+CAP provides intrinsic extensibility, which means all your entities and services are extensible by default.
+
+Your SaaS app becomes the **base app** for extensions by your customers, and your data model the **base model**.
+
+### 1. Enable Extensibility
+
+Extensibility is enabled by running this command in your project root:
+
+```sh
+cds add extensibility
+```
+
+::: details Essentially, this automates the following stepsβ¦
+
+1. It adds an `@sap/cds-mtxs` package dependency:
+
+```sh
+npm add @sap/cds-mtxs
+```
+
+2. It switches on cds.requires.extensibility: true in your _package.json_:
+
+::: code-group
+
+```json [package.json]
+{
+ "name": "@capire/orders",
+ "version": "1.0.0",
+ "dependencies": {
+ "@capire/common": "*",
+ "@sap/cds": ">=5",
+ "@sap/cds-mtxs": "^1"
+ },
+ "cds": {
+ "requires": {
+ "extensibility": true // [!code focus]
+ }
+ }
+}
+```
+
+:::
+
+If `@sap/cds-mtxs` is newly added to your project install the dependencies:
+
+```sh
+npm i
+```
+
+### 2. Restrict Extension Points { #restrictions }
+
+Normally, you'll want to restrict which services or entities your SaaS customers are allowed to extend and to what degree they may do so. Take a look at the following configuration:
+
+::: code-group
+
+```jsonc [mtx/sidecar/package.json]
+{
+ "cds": {
+ "requires": {
+ "cds.xt.ExtensibilityService": {
+ "element-prefix": ["x_"],
+ "extension-allowlist": [
+ {
+ "for": ["sap.capire.orders"],
+ "kind": "entity",
+ "new-fields": 2
+ },
+ {
+ "for": ["OrdersService"],
+ "new-entities": 2
+ }
+ ]
+ }
+ }
+ }
+}
+```
+
+:::
+
+This enforces the following restrictions:
+
+- All new elements have to start with `x_` β to avoid naming conflicts.
+- Only entities in namespace `sap.capire.orders` can be extended, with a maximum 2 new fields allowed.
+- Only the `OrdersService` can be extended, with a maximum of 2 new entities allowed.
+
+[Learn more about extension restrictions.](../multitenancy/mtxs#extensibility-config){.learn-more}
+
+### 3. Provide Template Projects {#templates}
+
+To jumpstart your customers with extension projects, it's beneficial to provide a template project. Including this template with your application and making it available as a downloadable archive not only simplifies their work but also enhances their experience.
+
+#### Create an Extension Project (Template)
+
+Extension projects are standard CAP projects extending the SaaS application. Create one for your SaaS app following these steps:
+
+1. Create a new CAP project β `orders-ext` in our walkthrough:
+
+ ```sh
+ cd ..
+ cds init orders-ext
+ code orders-ext # open in VS Code
+ ```
+
+2. Add this to your _package.json_:
+
+ ::: code-group
+
+ ```jsonc [package.json]
+ {
+ "name": "@capire/orders-ext",
+ "extends": "@capire/orders",
+ "workspaces": [ ".base" ]
+ }
+ ```
+
+ :::
+
+- `name` identifies the extension within a SaaS subscription; extension developers can choose the value freely.
+- `extends` is the name by which the extension model will refer to the base model. This must be a valid npm package name as it will be used by `cds pull` as a package name for the base model. It doesn't have to be a unique name, nor does it have to exist in a package registry like npmjs, as it will only be used locally.
+- `workspaces` is a list of folders including the one where the base model is stored. `cds pull` will add this property automatically if not already present.
+
+::: details Uniqueness of base-model nameβ¦
+
+You use the `extends` property as the name of the base model in your extension project. Currently, it's not an issue if the base model name isn't unique. However, to prevent potential conflicts, we recommend using a unique name for the base model.
+
+:::
+
+#### Add Sample Content
+
+Create a new file _app/extensions.cds_ and fill in this content:
+
+
+::: code-group
+
+```cds [app/extensions.cds]
+namespace x_orders.ext; // only applies to new entities defined below
+using { OrdersService, sap.capire.orders.Orders } from '@capire/orders';
+
+extend Orders with {
+ x_new_field : String;
+}
+
+// -------------------------------------------
+// Fiori Annotations
+
+annotate Orders:x_new_field with @title: 'New Field';
+annotate OrdersService.Orders with @UI.LineItem: [
+ ... up to { Value: OrderNo },
+ { Value : x_new_field },
+ ...
+];
+```
+
+:::
+
+The name of the _.cds_ file can be freely chosen. Yet, for the build system to work out of the box, it must be in either the `app`, `srv`, or `db` folder.
+
+[Learn more about project layouts.](../../get-started/#project-structure){.learn-more}
+
+::: tip Keep it simple
+We recommend putting all extension files into `./app` and removing `./srv` and `./db` from extension projects.
+
+You may want to consider [separating concerns](../domain-modeling#separation-of-concerns) by putting all Fiori annotations into a separate _./app/fiori.cds_.
+:::
+
+#### Add Test Data
+
+To support [quick-turnaround tests of extensions](#test-locally) using `cds watch`, add some test data. In your template project, create a file _test/data/sap.capire.orders-Orders.csv_ like that:
+
+::: code-group
+
+```csv [test/data/sap.capire.orders-Orders.csv]
+ID;createdAt;buyer;OrderNo;currency_code;
+7e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-01-31;john.doe@test.com;1;EUR
+64e718c9-ff99-47f1-8ca3-950c850777d4;2019-01-30;jane.doe@test.com;2;EUR
+```
+
+:::
+
+#### Add a Readme
+
+Include additional documentation for the extension developer in a _README.md_ file inside the template project.
+::: code-group
+
+```md [README.md]
+# Getting Started
+
+Welcome to your extension project to `@capire/orders`.
+
+It contains these folders and files, following our recommended project layout:
+
+| File or Folder | Purpose |
+|----------------|--------------------------------|
+| `app/` | all extensions content is here |
+| `test/` | all test content is here |
+| `package.json` | project configuration |
+| `readme.md` | this getting started guide |
+
+
+## Next Steps
+
+- `cds pull` the latest models from the SaaS application
+- edit [`./app/extensions.cds`](./app/extensions.cds) to add your extensions
+- `cds watch` your extension in local test-drives
+- `cds push` your extension to **test** tenant
+- `cds push` your extension to **prod** tenant
+
+
+## Learn More
+
+Learn more at https://cap.cloud.sap/docs/guides/extensibility/customization.
+```
+
+:::
+
+### 4. Provide Extension Guides {#guide}
+
+You should provide documentation to guide your customers through the steps to add extensions. This guide should provide application-specific information along the lines of the walkthrough steps presented in this guide.
+
+Here's a rough checklist what this guide should cover:
+
+
+- [How to set up test tenants](#prepare-an-extension-tenant) for extension projects
+- [How to assign requisite roles](#prepare-an-extension-tenant) to extension developers
+- [How to start extension projects](#start-ext-project) from [provided templates](#templates)
+- [How to find deployed app urls](#pull-base) of test and prod tenants
+- [What can be extended?](#about-extension-models) β which services, entities, ...
+- [With enclosed documentation](../../cds/cdl#doc-comment) to the models for these services and entities.
+
+### 5. Deploy Application
+
+Before deploying your SaaS application to the cloud, you can [test-drive it locally](../multitenancy/#test-locally).
+Prepare this by going back to your app with `cd orders`.
+
+With your application enabled and prepared for extensibility, you are ready to deploy the application as described in the [Deployment Guide](../deployment/).
+
+## As a SaaS Customer {#prep-as-operator}
+
+The following sections provide step-by-step instructions on adding extensions.
+All steps are based on our Orders Management sample which can be [started locally for testing](../multitenancy/#test-locally).
+
+::: details On BTPβ¦
+
+To extend a SaaS app deployed to BTP, you'll need to subscribe to it [through the BTP cockpit](../multitenancy/#subscribe).
+
+Refer to the [Deployment Guide](../deployment/to-cf) for more details on remote deployments.
+
+Also, you have to replace local URLs used in `cds` commands later with the URL of the deployed App Router.
+Use a passcode to authenticate and authorize you.
+Refer to the section on [`cds login`](#cds-login) for a simplified workflow.
+
+:::
+
+### 1. Subscribe to SaaS App
+
+It all starts with a customer subscribing to a SaaS application. In a productive application this is usually triggered by the platform to which the customer is logged on. The platform is using a technical user to call the application subscription API.
+In your local setup, you can simulate this with a [mock user](../../node.js/authentication#mock-users) `yves`.
+
+1. In a new terminal, subscribe as tenant `t1`:
+
+ ```sh
+ cds subscribe t1 --to http://localhost:4005 -u yves:
+ ```
+
+ Please note that the URL used for the subscription command is the sidecar URL, if a sidecar is used.
+ Learn more about tenant subscriptions [via the MTX API for local testing](../multitenancy/mtxs#put-tenant).{.learn-more}
+
+2. Verify that it worked by opening the [Orders Management Fiori UI](http://localhost:4004/orders/index.html#manage-orders) in a **new private browser window** and log in as `carol`, which is assigned to tenant `t1`.
+
+{.mute-dark}
+
+### 2. Prepare an Extension Tenant {#prepare-an-extension-tenant}
+
+In order to test-drive and validate the extension before activating to production, you'll first need to set up a test tenant. This is how you simulate it in your local setup:
+
+1. Set up a **test tenant** `t1-ext`
+
+ ```sh
+ cds subscribe t1-ext --to http://localhost:4005 -u yves:
+ ```
+
+2. Assign **extension developers** for the test tenant.
+
+ > As you're using mocked auth, simulate this step by adding the following to the SaaS app's _package.json_, assigning user `bob` as extension developer for tenant `t1-ext`:
+
+ ::: code-group
+
+ ```json [package.json]
+ {
+ "cds": {
+ "requires": {
+ "auth": {
+ "users": {
+ "bob": {
+ "tenant": "t1-ext",
+ "roles": ["cds.ExtensionDeveloper"]
+ }
+ }
+ }
+ }
+ }
+ }
+ ```
+
+ :::
+
+### 3. Start an Extension Project {#start-ext-project}
+
+Extension projects are standard CAP projects extending the subscribed application. SaaS providers usually provide **application-specific templates**, which extension developers can download and open in their editor.
+
+You can therefore use the extension template created in your walkthrough [as SaaS provider](#templates).
+Open the `orders-ext` folder in your editor. Here's how you do it using VS Code:
+
+```sh
+code ../orders-ext
+```
+
+{.ignore-dark}
+
+### 4. Pull the Latest Base Model {#pull-base}
+
+Next, you need to download the latest base model.
+
+```sh
+cds pull --from http://localhost:4005 -u bob:
+```
+
+> Run `cds help pull` to see all available options.
+
+This downloads the base model as a package into an npm workspace folder `.base`. The actual folder name is taken from the `workspaces` configuration. It also prepares the extension _package.json_ to reference the base model, if the extension template does not already do so.
+
+::: details See what `cds pull` doesβ¦
+
+1. Gets the base-model name from the extension _package.json_, property `extends`.
+
+ If the previous value is not a valid npm package name, it gets changed to `"base-model"`. In this case, existing source files may have to be manually adapted. `cds pull` will notify you in such cases.
+
+2. It fetches the base model from the SaaS app.
+3. It saves the base model in a subdirectory `.base` of the extension project.
+
+ This includes file _.base/package.json_ describing the base model as an npm package, including a `"name"` property set to the base-model name.
+
+4. In the extension _package.json_:
+
+ - It configures `.base` as an npm workspace folder.
+ - It sets the `extends` property to the base-model name.
+
+:::
+
+### 5. Install the Base Model
+
+To make the downloaded base model ready for use in your extension project, install it as a package:
+
+```sh
+npm install
+```
+
+This will link the base model in the workspace folder to the subdirectory `node_modules/@capire/orders` (in this example).
+
+### 6. Write the Extension {#write-extension }
+
+Edit the file _app/extensions.cds_ and replace its content with the following:
+
+::: code-group
+
+```cds [app/extensions.cds]
+namespace x_orders.ext; // for new entities like SalesRegion below
+using { OrdersService, sap, sap.capire.orders.Orders } from '@capire/orders';
+
+extend Orders with { // 2 new fields....
+ x_priority : String enum {high; medium; low} default 'medium';
+ x_salesRegion : Association to x_SalesRegion;
+}
+
+entity x_SalesRegion : sap.common.CodeList { // Value Help
+ key code : String(11);
+}
+
+
+// -------------------------------------------
+// Fiori Annotations
+
+annotate Orders:x_priority with @title: 'Priority';
+annotate x_SalesRegion:name with @title: 'Sales Region';
+
+annotate OrdersService.Orders with @UI.LineItem: [
+ ... up to { Value: OrderNo },
+ { Value: x_priority },
+ { Value: x_salesRegion.name },
+ ...
+];
+```
+
+:::
+
+[Learn more about what you can do in CDS extension models](#about-extension-models){.learn-more}
+
+
+::: tip
+Make sure **no syntax errors** are shown in the [CDS editor](../../tools/cds-editors#vscode) before going on to the next steps.
+:::
+
+### 7. Test-Drive Locally {#test-locally }
+
+To conduct an initial test of your extension, run it locally with `cds watch`:
+
+```sh
+cds watch --port 4006
+```
+
+> This starts a local Node.js application server serving your extension along with the base model and supplied test data stored in an in-memory database.
+> It does not include any custom application logic though.
+
+#### Add Local Test Data
+
+To improve local test drives, you can add _local_ test data for extensions.
+
+Edit the template-provided file `test/data/sap.capire.orders-Orders.csv` and add data for the new fields as follows:
+
+::: code-group
+
+```csv [test/data/sap.capire.orders-Orders.csv]
+ID;createdAt;buyer;OrderNo;currency_code;x_priority;x_salesRegion_code
+7e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-01-31;john.doe@test.com;1;EUR;high;EMEA
+64e718c9-ff99-47f1-8ca3-950c850777d4;2019-01-30;jane.doe@test.com;2;EUR;low;APJ
+```
+
+:::
+
+Create a new file `test/data/x_orders.ext-x_SalesRegion.csv` with this content:
+
+::: code-group
+
+```csv [test/data/x_orders.ext-x_SalesRegion.csv]
+code;name;descr
+AMER;Americas;North, Central and South America
+EMEA;Europe, the Middle East and Africa;Europe, the Middle East and Africa
+APJ;Asia Pacific and Japan;Asia Pacific and Japan
+```
+
+:::
+
+#### Verify the Extension
+
+Verify your extensions are applied correctly by opening the [Orders Fiori Preview](http://localhost:4006/$fiori-preview/OrdersService/Orders#preview-app) in a **new private browser window**, log in as `bob`, and see columns _Priority_ and _Sales Region_ filled as in the following screenshot:
+
+{.mute-dark}
+
+> Note: the screenshot includes local test data, added as explained below.
+
+This test data will only be deployed to the local sandbox and not be processed during activation to the productive environment.
+
+### 8. Push to Test Tenant {#push-extension }
+
+Let's push your extension to the deployed application in your test tenant for final verification before pushing to production.
+
+```sh
+cds push --to http://localhost:4005 -u bob:
+```
+
+::: tip
+`cds push` runs a `cds build` on your extension project automatically.
+:::
+
+::: details Prepacked extensions
+To push a ready-to-use extension archive (.tar.gz or .tgz), run `cds push `. The argument can be a local path to the archive or a URL to download it from.
+Run `cds help push` to see all available options.
+:::
+
+> You pushed the extension with user `bob`, which in your local setup ensures they are sent to your test tenant `t1-ext`, not the production tenant `t1`.
+
+::: details Building extensions
+
+`cds build` compiles the extension model and validates the constraints defined by the SaaS application, for example, it checks if the entities are extendable.
+It will fail in case of compilation or validation errors, which will in turn abort `cds push`.
+
+_Warning_ messages related to the SaaS application base model are reclassified as _info_ messages. As a consequence they will not be shown by default.
+Execute `cds build --log-level info` to display all messages, although they should not be of interest for the extension developer.
+
+:::
+
+#### Verify the Extension {#test-extension }
+
+Verify your extensions are applied correctly by opening the [Order Management UI](http://localhost:4004/orders/index.html#manage-orders) in a **new private browser window**, log in as `bob`, and check that columns _Priority_ and _Sales Region_ are displayed as in the following screenshot. Also, check that there's content with a proper label in the _Sales Region_ column.
+
+{.mute-dark}
+
+### 9. Add Data {#add-data}
+
+After pushing your extension, you have seen that the column for _Sales Region_ was added, but is not filled.
+To change this, you need to provide initial data with your extension. Copy the data file that you created before from `test/data/` to `db/data/` and push the extension again.
+
+[Learn more about adding data to extensions](#add-data-to-extensions) {.learn-more}
+
+### 10. Activate the Extension {#push-to-prod}
+
+Finally, after all tests, verifications and approvals are in place, you can push the extension to your production tenant:
+
+```sh
+cds push --to http://localhost:4005 -u carol:
+```
+
+> You pushed the extension with [mock user](../../node.js/authentication#mock-users) `carol`, which in your local setup ensures they are sent to your **production** tenant `t1`.
+
+::: tip Simplify your workflow with `cds pull` and `cds push`
+
+Particularly when extending deployed SaaS apps, refer to [`cds login`](#cds-login) to save project settings and authentication data for later reuse.
+
+:::
+
+# Appendices
+
+
+
+## Configuring App Router {#app-router}
+
+In a deployed multitenant SaaS application, you need to set up the App Router correctly. This setup lets the CDS command-line utilities connect to the MTX Sidecar without needing to authenticate again. If you haven't used both the `cds add multitenancy` and `cds add approuter` commands, it's likely that you'll need to tweak the App Router configuration. You can do this by adding a route to the MTX Sidecar.
+
+```json [app/router/xs-app.json]
+{
+ "routes": [
+ {
+ "source": "^/-/cds/.*",
+ "destination": "mtx-api",
+ "authenticationType": "none"
+ }
+ ]
+}
+```
+
+This ensures that the App Router doesn't try to authenticate requests to MTX Sidecar, which would fail. Instead, the Sidecar authenticates requests itself.
+
+## About Extension Models
+
+This section explains in detail about the possibilities that the _CDS_ languages provides for extension models.
+
+All names are subject to [extension restrictions defined by the SaaS app](../multitenancy/mtxs#extensibility-config).
+
+### Extending the Data Model
+
+Following [the extend directive](../../cds/cdl#extend) it is pretty straightforward to extend the application with the following new artifacts:
+
+- Extend existing entities with new (simple) fields.
+- Create new entities.
+- Extend existing entities with new associations.
+- Add compositions to existing or new entities.
+- Supply new or existing fields with default values, range checks, or value list (enum) checks.
+- Define a mandatory check on new or existing fields.
+- Define new unique constraints on new or existing entities.
+
+```cds
+using {sap.capire.bookshop, sap.capire.orders} from '@capire/fiori';
+using {
+ cuid, managed, Country, sap.common.CodeList
+} from '@sap/cds/common';
+
+namespace x_bookshop.extension;
+
+// extend existing entity
+extend orders.Orders with {
+ x_Customer : Association to one x_Customers;
+ x_SalesRegion : Association to one x_SalesRegion;
+ x_priority : String @assert.range enum {high; medium; low} default 'medium';
+ x_Remarks : Composition of many x_Remarks on x_Remarks.parent = $self;
+}
+// new entity - as association target
+entity x_Customers : cuid, managed {
+ email : String;
+ firstName : String;
+ lastName : String;
+ creditCardNo : String;
+ dateOfBirth : Date;
+ status : String @assert.range enum {platinum; gold; silver; bronze} default 'bronze';
+ creditScore : Decimal @assert.range: [ 1.0, 100.0 ] default 50.0;
+ PostalAddresses : Composition of many x_CustomerPostalAddresses on PostalAddresses.Customer = $self;
+}
+
+// new unique constraint (secondary index)
+annotate x_Customers with @assert.unique: { email: [ email ] } {
+ email @mandatory; // mandatory check
+}
+
+// new entity - as composition target
+entity x_CustomerPostalAddresses : cuid, managed {
+ Customer : Association to one x_Customers;
+ description : String;
+ street : String;
+ town : String;
+ country : Country;
+}
+
+// new entity - as code list
+entity x_SalesRegion: CodeList {
+ key regionCode : String(11);
+}
+
+// new entity - as composition target
+entity x_Remarks : cuid, managed {
+ parent : Association to one orders.Orders;
+ number : Integer;
+ remarksLine : String;
+}
+```
+
+::: tip
+This example provides annotations for business logic handled automatically by CAP as documented in [_Providing Services_](../providing-services#input-validation).
+:::
+Learn more about the [basic syntax of the `annotate` directive](../../cds/cdl#annotate) {.learn-more}
+
+### Extending the Service Model
+
+In the existing in `OrdersService`, the new entities `x_CustomerPostalAddresses` and `x_Remarks` are automatically included since they are targets of the corresponding _compositions_.
+
+The new entities `x_Customers` and `x_SalesRegion` are [autoexposed](../providing-services#auto-exposed-entities) in a read-only way as [CodeLists](../../cds/common#aspect-codelist). Only if wanted to _change_ it, you would need to expose them explicitly:
+
+```cds
+using { OrdersService } from '@capire/fiori';
+
+extend service OrdersService with {
+ entity x_Customers as projection on extension.x_Customers;
+ entity x_SalesRegion as projection on extension.x_SalesRegion;
+}
+```
+
+### Extending UI Annotations
+
+The following snippet demonstrates which UI annotations you need to expose your extensions to the SAP Fiori elements UI.
+
+Add UI annotations for the completely new entities `x_Customers, x_CustomerPostalAddresses, x_SalesRegion, x_Remarks`:
+
+```cds
+using { OrdersService } from '@capire/fiori';
+
+// new entity -- draft enabled
+annotate OrdersService.x_Customers with @odata.draft.enabled;
+
+// new entity -- titles
+annotate OrdersService.x_Customers with {
+ ID @(
+ UI.Hidden,
+ Common : {Text : email}
+ );
+ firstName @title : 'First Name';
+ lastName @title : 'Last Name';
+ email @title : 'Email';
+ creditCardNo @title : 'Credit Card No';
+ dateOfBirth @title : 'Date of Birth';
+ status @title : 'Status';
+ creditScore @title : 'Credit Score';
+}
+
+// new entity -- titles
+annotate OrdersService.x_CustomerPostalAddresses with {
+ ID @(
+ UI.Hidden,
+ Common : {Text : description}
+ );
+ description @title : 'Description';
+ street @title : 'Street';
+ town @title : 'Town';
+ country @title : 'Country';
+}
+
+// new entity -- titles
+annotate x_SalesRegion : regionCode with @(
+ title : 'Region Code',
+ Common: { Text: name, TextArrangement: #TextOnly }
+);
+
+
+// new entity in service -- UI
+annotate OrdersService.x_Customers with @(UI : {
+ HeaderInfo : {
+ TypeName : 'Customer',
+ TypeNamePlural : 'Customers',
+ Title : { Value : email}
+ },
+ LineItem : [
+ {Value : firstName},
+ {Value : lastName},
+ {Value : email},
+ {Value : status},
+ {Value : creditScore}
+ ],
+ Facets : [
+ {$Type: 'UI.ReferenceFacet', Label: 'Main', Target : '@UI.FieldGroup#Main'},
+ {$Type: 'UI.ReferenceFacet', Label: 'Customer Postal Addresses', Target: 'PostalAddresses/@UI.LineItem'}
+],
+ FieldGroup #Main : {Data : [
+ {Value : firstName},
+ {Value : lastName},
+ {Value : email},
+ {Value : status},
+ {Value : creditScore}
+ ]}
+});
+
+// new entity -- UI
+annotate OrdersService.x_CustomerPostalAddresses with @(UI : {
+ HeaderInfo : {
+ TypeName : 'CustomerPostalAddress',
+ TypeNamePlural : 'CustomerPostalAddresses',
+ Title : { Value : description }
+ },
+ LineItem : [
+ {Value : description},
+ {Value : street},
+ {Value : town},
+ {Value : country_code}
+ ],
+ Facets : [
+ {$Type: 'UI.ReferenceFacet', Label: 'Main', Target : '@UI.FieldGroup#Main'}
+ ],
+ FieldGroup #Main : {Data : [
+ {Value : description},
+ {Value : street},
+ {Value : town},
+ {Value : country_code}
+ ]}
+}) {};
+
+// new entity -- UI
+annotate OrdersService.x_SalesRegion with @(
+ UI: {
+ HeaderInfo: {
+ TypeName : 'Sales Region',
+ TypeNamePlural : 'Sales Regions',
+ Title : { Value : regionCode }
+ },
+ LineItem: [
+ {Value: regionCode},
+ {Value: name},
+ {Value: descr}
+ ],
+ Facets: [
+ {$Type: 'UI.ReferenceFacet', Label: 'Main', Target: '@UI.FieldGroup#Main'}
+ ],
+ FieldGroup#Main: {
+ Data: [
+ {Value: regionCode},
+ {Value: name},
+ {Value: descr}
+ ]
+ }
+ }
+) {};
+
+// new entity -- UI
+annotate OrdersService.x_Remarks with @(
+ UI: {
+ HeaderInfo: {
+ TypeName : 'Remark',
+ TypeNamePlural : 'Remarks',
+ Title : { Value : number }
+ },
+ LineItem: [
+ {Value: number},
+ {Value: remarksLine}
+ ],
+ Facets: [
+ {$Type: 'UI.ReferenceFacet', Label: 'Main', Target: '@UI.FieldGroup#Main'}
+ ],
+ FieldGroup#Main: {
+ Data: [
+ {Value: number},
+ {Value: remarksLine}
+ ]
+ }
+ }
+) {};
+```
+
+#### Extending Array Values
+
+Extend the existing UI annotation of the existing `Orders` entity with new extension fields and new facets using the special [syntax for array-valued annotations](../../cds/cdl#extend-array-annotations).
+
+```cds
+// extend existing entity Orders with new extension fields and new composition
+annotate OrdersService.Orders with @(
+ UI: {
+ LineItem: [
+ ... up to { Value: OrderNo }, // head
+ {Value: x_Customer_ID, Label:'Customer'}, //> extension field
+ {Value: x_SalesRegion.regionCode, Label:'Sales Region'}, //> extension field
+ {Value: x_priority, Label:'Priority'}, //> extension field
+ ..., // rest
+ ],
+ Facets: [...,
+ {$Type: 'UI.ReferenceFacet', Label: 'Remarks', Target: 'x_Remarks/@UI.LineItem'} // new composition
+ ],
+ FieldGroup#Details: {
+ Data: [...,
+ {Value: x_Customer_ID, Label:'Customer'}, // extension field
+ {Value: x_SalesRegion.regionCode, Label:'Sales Region'}, // extension field
+ {Value: x_priority, Label:'Priority'} // extension field
+ ]
+ }
+ }
+);
+
+```
+
+The advantage of this syntax is that you do not have to replicate the complete array content of the existing UI annotation, you only have to add the delta.
+
+#### Semantic IDs
+
+Finally, exchange the display ID (which is by default a GUID) of the new `x_Customers` entity with a human readable text which in your case is given by the unique property `email`.
+
+```cds
+// new field in existing service -- exchange ID with text
+annotate OrdersService.Orders:x_Customer with @(
+ Common: {
+ //show email, not id for Customer in the context of Orders
+ Text: x_Customer.email , TextArrangement: #TextOnly,
+ ValueList: {
+ Label: 'Customers',
+ CollectionPath: 'x_Customers',
+ Parameters: [
+ { $Type: 'Common.ValueListParameterInOut',
+ LocalDataProperty: x_Customer_ID,
+ ValueListProperty: 'ID'
+ },
+ { $Type: 'Common.ValueListParameterDisplayOnly',
+ ValueListProperty: 'email'
+ }
+ ]
+ }
+ }
+);
+```
+
+### Localizable Texts
+
+To externalize translatable texts, use the same approach as for standard applications, that is, create a _i18n/i18n.properties_ file:
+
+::: code-group
+
+```properties [i18n/i18n.properties]
+SalesRegion_name_col = Sales Region
+Orders_priority_col = Priority
+...
+```
+
+:::
+
+Then replace texts with the corresponding `{i18n>...}` keys from the properties file.
+Make sure to run `cds build` again.
+
+Properties files must be placed in the `i18n` folder. If an entry with the same key exists in the SaaS application, the translation of the extension has preference.
+
+> This feature is available with `@sap/cds` 6.3.0 or higher.
+
+[Learn more about localization](../i18n){.learn-more}
+
+## Simplify Your Workflow With `cds login` {#cds-login}
+
+As a SaaS extension developer, you have the option to log in to the SaaS app and thus authenticate only once.
+This allows you to re-run `cds pull` and `cds push` against the app without repeating the same options over and over again β and you can avoid generating a passcode every time.
+
+Achieve this by running `cds login` once. This command fetches tokens using OAuth2 from XSUAA and saves them for later use. For convenience, further settings for the current project are also stored, so you don't have to provide them again (such as the app URL and tenant subdomain).
+
+### Where Tokens Are Stored
+
+Tokens are saved in the desktop keyring by default
+(libsecret on Linux, Keychain Access on macOS, or Credential Vault on Windows).
+
+Using the keyring is more secure because, depending on the platform, you can lock and unlock it, and data saved by `cds login` may be inaccessible to other applications you run.
+
+> For details, refer to the documentation of the keyring implementation used on your development machine.
+
+`cds login` therefore uses the keyring by default. To enable this, you need to install an additional Node.js module, [_keytar_](https://www.npmjs.com/package/keytar):
+
+```sh
+npm i -g keytar
+```
+
+If you decide against using the keyring, you can request `cds login` to write to a plain-text file by appending `--plain`.
+
+::: tip Switching to and from plain-text
+Once usage of the `--plain` option changes for a given SaaS app, `cds login` migrates pre-existing authentication data from the previous storage to the new storage.
+:::
+
+::: warning Handle secrets with caution
+Local storage of authentication data incurs a security risk: a potential malicious, local process might be able to perform actions you're authorized for, with the SaaS app, as your tenant.
+:::
+
+> In SAP Business Application Studio, plain-text storage is enforced when using `cds login`, since no desktop keyring is available. The plain-text file resides in encrypted storage.
+
+### How to Login
+
+If you work with Cloud Foundry (CF) and you have got the `cf` client installed, you can call `cds login` with just a passcode. The command runs the `cf` client to determine suitable apps from the org and space that you're logged in to. This allows you to interactively choose the login target from a list of apps and their respective URLs.
+
+To log in to the SaaS app in this way, first change to the folder you want to use for your extension project. Then run the following command (the one-time passcode will be prompted interactively if omitted):
+
+```sh
+cds login [-p ]
+```
+
+:::details Advanced options
+
+If you need to call `cds login` automatically without user interaction, you may use the [Client Credentials](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/) grant, which does not require a passcode.
+
+You can then omit the `-p ` option but will instead have to provide the Client ID and a specific form of client secret to authenticate.
+
+Obtain these two from the `VCAP_SERVICES` environment variable in your deployed MTX server (`@sap/cds-mtxs`). In the JSON value, navigate to `xsuaa[0].credentials`.
+
+- If you find a `key` property (Private Key of the Client Certificate), XSUAA is configured to use X.509 (mTLS). Use this Private Key by specifying `cds login β¦ -m [:key]`.
+
+- Otherwise, find the Client Secret in the `clientsecret` property and use `cds login β¦ -c [:]` in an analogous way.
+
+**Note:** The `key` and `clientsecret` properties are secrets that should not be stored in an unsafe location in productive scenarios!
+
+[Learn more about environment variables / `VCAP_Services`.](/node.js/cds-connect#bindings-in-cloud-platforms){.learn-more}
+
+If you leave out the respective secret (enclosed in square brackets above), you will be prompted to enter it interactively.
+This can be used to feed the secret from the environment to `cds login` via standard input, like so:
+
+```sh
+echo $MY_KEY | cds login β¦ -m
+```
+
+:::
+
+For a synopsis of all options, run `cds help login`.
+
+:::details Login without CF CLI
+
+If you don't work with CF CLI, additionally provide the application URL and the subdomain as these can't be determined automatically:
+
+```sh
+cds login [] -s
+```
+
+The `` is the URL that you get in your subscriber account when you subscribe to an application. You find the `` in the overview page of your subaccount in the SAP BTP Cockpit:
+
+
+
+:::
+
+::: tip Multiple targets
+Should you later want to extend other SaaS applications, you can log in to them as well, and it won't affect your other logins.
+Logins are independent of each other, and `cds pull` etc. will be authenticated based on the requested target.
+:::
+
+### Simplified Workflow
+
+Once you've logged in to the SaaS app, you can omit the passcode, the app URL, and the tenant subdomain, so in your development cycle you can run:
+
+```sh
+cds pull
+# develop your extension
+cds push
+# develop your extension
+cds push
+# β¦
+```
+
+::: tip Override saved values with options
+For example, run `cds push -s -p ` to activate your extension in another subdomain.
+This usage of `cds push` may be considered a kind of cross-client transport mechanism.
+:::
+
+### Refreshing Tokens
+
+Tokens have a certain lifespan, after which they lose validity. To save you the hassle, `cds login` also stores the refresh token sent by XSUAA alongside the token (depending on configuration) and uses it to automatically renew the token after it has expired. By default, refresh tokens expire much later than the token itself, allowing you to work without re-entering passcodes for multiple successive days.
+
+### Cleaning Up
+
+To remove locally saved authentication data and optionally, the project settings, run `cds logout` inside your extension project folder.
+
+Append `--delete-settings` to include saved project settings for the current project folder as well.
+
+`cds help logout` is available for more details.
+
+::: tip
+When your role-collection assignments have changed, run `cds logout` followed by `cds login` in order to fetch a token containing the new set of scopes.
+:::
+
+### Debugging
+
+In case something unexpected happens, set the variable `DEBUG=cli` in your shell environment before re-running the corresponding command.
+
+::: code-group
+
+```sh [Mac/Linux]
+export DEBUG="cli"
+```
+
+```cmd [Windows]
+set DEBUG=cli
+```
+
+```powershell [Powershell]
+Set-Variable -Name "DEBUG" -Value "cli"
+```
+
+:::
+
+## Add Data to Extensions
+
+As described in [Add Data](#add-data), you can provide local test data and initial data for your extension. In this guide we copied local data from the `test/data` folder into the `db/data` folder. When using SQLite, this step can be further simplified. For `sap.capire.orders-Orders.csv`, just add the _new_ columns along with the primary key:
+`
+::: code-group
+
+```csv [sap.capire.orders-Orders.csv]
+ID;x_priority;x_salesRegion_code
+7e2f2640-6866-4dcf-8f4d-3027aa831cad;high;EMEA
+64e718c9-ff99-47f1-8ca3-950c850777d4;low;APJ
+```
+
+:::
+
+::: warning _β Warning_
+Adding data only for the missing columns doesn't work when using SAP HANA as a database. With SAP HANA, you always have to provide the full set of data.
+:::
+
+
diff --git a/guides/extensibility/feature-toggles.md b/guides/extensibility/feature-toggles.md
new file mode 100644
index 000000000..6f7448b16
--- /dev/null
+++ b/guides/extensibility/feature-toggles.md
@@ -0,0 +1,427 @@
+---
+# layout: cookbook
+label: Feature Toggles
+synopsis: >
+ Toggled features are pre-built extensions built by the provider of a SaaS application, which can be switched on selectively per subscriber.
+breadcrumbs:
+ - Cookbook
+ - Extensibility
+ - Feature Toggles
+status: released
+impl-variants: true # to enable Node.js/Java toggle
+---
+
+# Feature Toggles
+
+{{$frontmatter?.synopsis}}
+
+
+
+## Introduction and Overview
+
+CAP feature-toggled aspects allow SaaS providers to create pre-built features as CDS models, extending the base models with new fields, entities, as well as annotations for SAP Fiori UIs. These features can be assigned to individual SaaS customers (tenants), users, and requests and are then activated dynamically at runtime, as illustrated in the following figure.
+
+
+
+### Get `cloud-cap-samples-java` for step-by-step Exercises {.java}
+
+The following steps will extend the [CAP samples for Java](https://github.com/SAP-samples/cloud-cap-samples-java) app to demonstrate how features can extend data models, services, as well as SAP Fiori UIs. If you want to exercise these steps, get [cloud-cap-samples-java](https://github.com/SAP-samples/cloud-cap-samples-java) before, and prepare to extend the *Fiori* app:
+
+
+
+Now, open the app in your editor, for example, for VS Code type:
+
+```sh
+code .
+```
+
+### Get `cap/samples` for Step-By-Step Exercises {.node}
+
+The following steps will extend the [cap/samples/fiori](https://github.com/sap-samples/cloud-cap-samples/blob/main/fiori) app to demonstrate how features can extend data models, services, as well as SAP Fiori UIs. If you want to exercise these steps, get [cap/samples](https://github.com/sap-samples/cloud-cap-samples) before, and prepare to extend the *fiori* app:
+
+```sh
+git clone https://github.com/sap-samples/cloud-cap-samples samples
+cd samples
+npm install
+```
+
+Now, open the `fiori` app in your editor, for example, by this if you're using VS Code on macOS:
+
+```sh
+code fiori
+```
+
+## Enable Feature Toggles {.node}
+
+### Add `@sap/cds-mtxs` Package Dependency
+
+For example, like this:
+
+```sh
+npm add @sap/cds-mtxs
+```
+
+### Switch on `cds.requires.toggles`
+
+Switch on feature toggle support by adding cds.requires.toggles: true.
+
+## Adding Features in CDS
+
+Add a subfolder per feature to folder *fts* and put `.cds` files into it. The name of the folder is the name you later on use in feature toggles to switch the feature on/off. In our samples app, we add two features `isbn` and `reviews` as depicted in the following screenshot:
+
+{.ignore-dark}
+
+> The name of the *.cds* files within the *fts/* subfolders can be freely chosen. All *.cds* files found in there will be served, with special handling for *index.cds* files, as usual.
+
+### Feature *fts/isbn*
+
+Create a file *fiori/fts/isbn/schema.cds* with this content:
+
+```cds
+using { CatalogService, sap.capire.bookshop.Books }
+from '../../app/browse/fiori-service';
+
+// Add new field `isbn` to Books
+extend Books with {
+ isbn : String @title:'ISBN';
+}
+
+// Display that new field in list on Fiori UI
+annotate CatalogService.Books with @(
+ UI.LineItem: [... up to {Value:author}, {Value:isbn}, ...]
+);
+```
+
+This feature adds a new field `isbn` to entity `Books` and extends corresponding SAP Fiori annotations to display this field in the *Browse Books* list view.
+
+::: tip
+Note that all features will be deployed to each tenant database in order to allow toggling per user/request.
+:::
+
+### Feature *fts/reviews*
+
+Create a file *fiori/fts/reviews/schema.cds* with this content:
+
+```cds
+using { CatalogService } from '../../app/browse/fiori-service';
+
+// Display existing field `rating` in list on Fiori UI
+annotate CatalogService.Books with @(
+ UI.LineItem: [... up to {Value:author}, {Value:rating}, ...]
+);
+```
+
+This feature extends corresponding SAP Fiori annotations to display already existing field `rating` in the *Browse Books* list view.
+
+### Limitations
+
+::: warning
+Note the following limitations for `.cds` files in features:
+
+- no `.cds` files in subfolders, for example, `fts/isbn/sub/file.cds`
+- no `using` dependencies between features
+- further limitations re `extend aspect` β to be documented
+:::
+
+## Toggling Features
+
+In principle, features can be toggled per request, per user, or per tenant; most commonly they'll be toggled per tenant, as demonstrated in the following.
+
+### In Development
+
+
+
+CAP Node.js' `mocked-auth` strategy has built-in support for toggling features per tenant, per user, or per request. To demonstrate toggling features per tenant, or user, you can add these lines of configuration to our `package.json` of the SAP Fiori app:
+
+```json
+{"cds":{
+ "requires": {
+ "auth": {
+ "users": {
+ "carol": { "tenant": "t1" },
+ "erin": { "tenant": "t2" },
+ "fred": { "tenant": "t2", "features":[] }
+ },
+ "tenants": {
+ "t1": { "features": ["isbn"] },
+ "t2": { "features": "*" }
+ }
+ }
+ }
+}}
+```
+
+
+
+
+
+CAP Java's [Mock User Authentication with Spring Boot](../../java/security#mock-users) allows to assign feature toggles to users based on the mock user configuration. To demonstrate toggling features per user, you can add these lines to the mock user configuration in the `srv/src/main/resources/application.yaml` file:
+
+```yaml
+cds:
+ security.mock.users:
+ - name: carol
+ features:
+ - isbn
+ - name: erin
+ features:
+ - isbn
+ - reviews
+ - name: fred
+ features:
+```
+
+
+
+In effect of this, for the user `carol` the feature `isbn` is enabled, for `erin`, the features `isbn` and `reviews` are enabled, and for the user `fred` all features are disabled.
+
+### In Production
+
+
+
+::: warning No features toggling for production yet
+Note that the previous sample is only for demonstration purposes. As user and tenant management is outside of CAP's scope, there's no out-of-the-box feature toggles provider for production yet. β Learn more about that in the following section [*Feature Vector Providers*](#feature-vector-providers).
+:::
+
+
+
+
+
+
+
+For productive use, the mock user configuration must not be used. The set of active features is determined per request by the [Feature Toggles Info Provider](../../java/reflection-api#feature-toggles-info-provider).
+
+You can register a [Custom Implementation](../../java/reflection-api#custom-implementation) as a Spring bean that computes the active feature set based on the request's `UserInfo` and `ParameterInfo`.
+
+
+
+
+
+## Test-Drive Locally {.node}
+
+To test feature toggles, just run your CAP server as usual, then log on with different users, assigned to different tenants, to see the effects.
+
+### Run `cds watch`
+
+Start the CAP server with `cds watch` as usual:
+
+```sh
+cds watch
+```
+
+β in the log output, note the line reporting:
+
+```js
+[cds] - serving cds.xt.ModelProviderService {
+ path: '/-/cds/model-provider',
+ impl: '@sap/cds/srv/model-provider.js'
+}
+```
+
+> The `ModelProviderService` is used by the runtime to get feature-enhanced models.
+
+### See Effects in SAP Fiori UIs {#test-fiori-node}
+
+To see the effects in the UIs open three anonymous browser windows, one for each user to log in, and:
+
+1. [Open SAP Fiori app in browser](http://localhost:4004/fiori-apps.html) and go to [Browse Books](http://localhost:4004/fiori-apps.html#Books-display).
+2. Log in as `carol` and see `ISBN` column in list.
+3. Log in as `erin` and see `Ratings` and `ISBN` columns in list.
+4. Log in as `fred` and no features for *Fred*, even though same tenant as *Erin*.
+
+For example the displayed UI should look like that for `erin`:
+
+
+
+## Model Provider in Sidecar
+
+The `ModelProviderService`, which is used for toggling features, is implemented in Node.js only. To use it with CAP Java apps, you run it in a so-called *MTX sidecar*. For a CAP Node.js project, this service is always run embedded with the main application.
+
+### Create Sidecar as Node.js Project
+
+An MTX sidecar is a standard, yet minimalistic Node.js CAP project. By default it's added to a subfolder *mtx/sidecar* within your main project, containing just a *package.json* file:
+
+::: code-group
+
+```json [mtx/sidecar/package.json]
+{
+ "name": "mtx-sidecar", "version": "0.0.0",
+ "dependencies": {
+ "@sap/cds": "^7",
+ "@sap/cds-mtxs": "^1",
+ "express": "^4"
+ },
+ "cds": {
+ "requires": {
+ "cds.xt.ModelProviderService": "in-sidecar"
+ },
+ "[development]": {
+ "requires": { "auth": "dummy" },
+ "server": { "port": 4005 }
+ }
+ }
+}
+```
+
+:::
+
+[Learn more about setting up **MTX sidecars**.](../multitenancy/mtxs#sidecars){.learn-more}
+
+### Add Remote Service Link to Sidecar
+
+
+
+::: tip
+In Node.js apps you usually don't consume services from the sidecar. The *ModelProviderService* is served both, embedded in the main app as well as in the sidecar. The following is documented for the sake of completeness only...
+:::
+
+You can use the `from-sidecar` preset to tell the CAP runtime to use the remote model provider from the sidecar:
+
+```json
+"cds":{
+ "requires": {
+ "toggles": true,
+ "cds.xt.ModelProviderService": "from-sidecar"
+ }
+}
+```
+
+[Learn more about configuring ModelProviderService.](../multitenancy/mtxs#model-provider-config){.learn-more}
+
+
+
+
+
+You need to configure the CAP Java application to request the CDS model from the Model Provider Service.
+This is done in the `application.yaml` file of your application.
+To enable the Model Provider Service for local development, add the following configuration to the `default` profile:
+
+```yaml
+cds:
+ model:
+ provider:
+ url: http://localhost:4005
+ # remove, in case you need tenant extensibility
+ extensibility: false
+```
+
+
+
+### Test-Drive Sidecar Locally
+
+With the setup as described in place, you can run the main app locally with the Model Provider as sidecar. Simply start the main app and the sidecar in two separate shells:
+
+**First, start the sidecar** as the main app now depends on the sidecar:
+
+```sh
+cds watch mtx/sidecar
+```
+
+**Then, start the main app** in the second shell:
+
+
+
+```sh
+cds watch
+```
+
+
+
+
+
+```sh
+mvn spring-boot:run
+```
+
+
+
+#### Remote `getCsn()` Calls to Sidecar at Runtime {.node}
+
+When you now run and use our application again as described in the previous section [See Effects in SAP Fiori UIs](#test-fiori-node), you can see in the trace logs that the main app sends `getCsn` requests to the sidecar, which in response to that reads and returns the main app's models. That means, the models from two levels up the folder hierarchy as configured by `root: ../..` for development.
+
+### See Effects in SAP Fiori UIs {#test-fiori-java .java}
+
+To see the effects in the UIs open three anonymous browser windows, one for each user to log in, and:
+
+1. [Open SAP Fiori app in browser](localhost:8080/fiori.html) and go to [Browse Books](localhost:8080/fiori.html#browse-books).
+2. Log in as `carol` and see `ISBN` column in list.
+3. Log in as `erin` and see `Ratings` and `ISBN` columns in list.
+4. Log in as `fred` and no features for *Fred*, even though same tenant as *Erin*.
+
+For example the displayed UI should look like that for `erin`:
+
+
+
+## Feature Vector Providers {.node}
+
+In principle, features can be toggled *per request* using the `req.features` property (`req` being the standard HTTP req object here, not the CAP runtimes `req` object). This property is expected to contain one of the following:
+
+- An array with feature names, for example, `['isbn','reviews']`.
+- A string with comma-separated feature names, for example, `'isbn,reviews'`.
+- An object with keys being feature names, for example, `{isbn:true,reviews:true}`.
+
+So, to add support for a specific feature toggles management you can add a simple Express.js middleware as follows, for example, in your `server.js`:
+
+```js
+const cds = require ('@sap/cds')
+cds.on('bootstrap', app => app.use ((req,res,next) => {
+ req.features = req.headers.features || 'isbn'
+ next()
+}))
+```
+
+## Feature-Toggled Custom Logic
+
+
+
+[Evaluate the `FeatureTogglesInfo` in custom code](../../java/reflection-api#using-feature-toggles-in-custom-code) to check if a feature is enabled:
+
+```java
+@Autowired FeatureTogglesInfo features;
+
+...
+
+if (features.isEnabled("discount")) {
+ // specific coding when feature 'discount' is enabled...
+}
+```
+
+
+
+
+
+Within your service implementations, you can react on feature toggles by inspecting `cds.context.features` like so:
+
+```js
+const { features } = cds.context
+if ('isbn' in features) {
+ // specific coding when feature 'isbn' is enabled...
+}
+if ('reviews' in features) {
+ // specific coding when feature 'reviews' is enabled...
+}
+// common coding...
+```
+
+Or alternatively:
+
+```js
+const { isbn, reviews } = cds.context.features
+if (isbn) {
+ // specific coding when feature 'isbn' is enabled...
+}
+if (reviews) {
+ // specific coding when feature 'reviews' is enabled...
+}
+// common coding...
+```
+
+
diff --git a/guides/extensibility/index.data.ts b/guides/extensibility/index.data.ts
new file mode 100644
index 000000000..6b4139e33
--- /dev/null
+++ b/guides/extensibility/index.data.ts
@@ -0,0 +1,11 @@
+import { basename } from 'node:path'
+import { createContentLoader } from 'vitepress'
+import filter from '../../.vitepress/theme/components/indexFilter.ts'
+
+const basePath = basename(__dirname)
+
+export default createContentLoader(`**/${basePath}/*.md`, {
+ transform(rawData) {
+ return filter(rawData, `/${basePath}/`)
+ }
+})
diff --git a/guides/extensibility/index.md b/guides/extensibility/index.md
new file mode 100644
index 000000000..79eab559d
--- /dev/null
+++ b/guides/extensibility/index.md
@@ -0,0 +1,39 @@
+---
+index: 77
+breadcrumbs:
+ - Cookbook
+ - Extensibility
+synopsis: >
+ Learn here about intrinsic capabilities to extend your applications in verticalization
+ and customization scenarios.
+status: released
+uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/e4a7559baf9f4e4394302442745edcd9.html
+---
+
+# Extensibility
+
+{{ $frontmatter.synopsis }}
+
+Extensibility of CAP applications is greatly fueled by **CDS Aspects**, which allow to easily extend existing models with new fields, entities, relationships, or new or overridden annotations [→ Learn more about using CDS Aspects in the Domain Modeling guide](../domain-modeling#separation-of-concerns).
+
+
+
+As illustrated in the graphic above, different parties can build and deploy CDS Aspects-based extensions:
+
+- **Customizations** β Customers/Subscribers of SaaS solutions need options to tailor these to their needs, again using CDS Aspects to add custom fields and entities.
+
+- **Toggled Features** β SaaS providers can offer pre-built enhancement features, which can be switched on selectively per tenant using Feature Toggles, for example, specialization for selected industries.
+
+- **Composition** β Finally, 3rd parties can provide pre-built extension packages for reuse, which customers can pick and compose into own solutions.
+
+- **Verticalization** β 3rd parties can provide verticalized versions of a given base application, which they can in turn operate as verticalized SaaS apps.
+
+
+
+The following guides give detailed information to each of these options.
+
+
+
+
diff --git a/guides/multitenancy/assets/book-changed-t1.png b/guides/multitenancy/assets/book-changed-t1.png
new file mode 100644
index 000000000..d5c1c664e
Binary files /dev/null and b/guides/multitenancy/assets/book-changed-t1.png differ
diff --git a/guides/multitenancy/assets/cockpit-routes-new-map.png b/guides/multitenancy/assets/cockpit-routes-new-map.png
new file mode 100644
index 000000000..f5799f2eb
Binary files /dev/null and b/guides/multitenancy/assets/cockpit-routes-new-map.png differ
diff --git a/guides/multitenancy/assets/cockpit-routes-new-mapped-overview.png b/guides/multitenancy/assets/cockpit-routes-new-mapped-overview.png
new file mode 100644
index 000000000..bc4b46eef
Binary files /dev/null and b/guides/multitenancy/assets/cockpit-routes-new-mapped-overview.png differ
diff --git a/guides/multitenancy/assets/cockpit-routes-new-overview.png b/guides/multitenancy/assets/cockpit-routes-new-overview.png
new file mode 100644
index 000000000..755848386
Binary files /dev/null and b/guides/multitenancy/assets/cockpit-routes-new-overview.png differ
diff --git a/guides/multitenancy/assets/cockpit-routes-new.png b/guides/multitenancy/assets/cockpit-routes-new.png
new file mode 100644
index 000000000..248c80010
Binary files /dev/null and b/guides/multitenancy/assets/cockpit-routes-new.png differ
diff --git a/guides/multitenancy/assets/cockpit-routes.png b/guides/multitenancy/assets/cockpit-routes.png
new file mode 100644
index 000000000..b29a27b1c
Binary files /dev/null and b/guides/multitenancy/assets/cockpit-routes.png differ
diff --git a/guides/multitenancy/assets/create-subaccount.png b/guides/multitenancy/assets/create-subaccount.png
new file mode 100644
index 000000000..3baeca082
Binary files /dev/null and b/guides/multitenancy/assets/create-subaccount.png differ
diff --git a/guides/multitenancy/assets/db_explorer.png b/guides/multitenancy/assets/db_explorer.png
new file mode 100644
index 000000000..c4e3f87df
Binary files /dev/null and b/guides/multitenancy/assets/db_explorer.png differ
diff --git a/guides/multitenancy/assets/domain_deployment.svg b/guides/multitenancy/assets/domain_deployment.svg
new file mode 100644
index 000000000..b73bfc961
--- /dev/null
+++ b/guides/multitenancy/assets/domain_deployment.svg
@@ -0,0 +1,531 @@
+
+
+
+
diff --git a/guides/multitenancy/assets/go-to-app.png b/guides/multitenancy/assets/go-to-app.png
new file mode 100644
index 000000000..7f3eed47f
Binary files /dev/null and b/guides/multitenancy/assets/go-to-app.png differ
diff --git a/guides/multitenancy/assets/migration-steps.drawio.svg b/guides/multitenancy/assets/migration-steps.drawio.svg
new file mode 100644
index 000000000..dbf28deb3
--- /dev/null
+++ b/guides/multitenancy/assets/migration-steps.drawio.svg
@@ -0,0 +1,167 @@
+
\ No newline at end of file
diff --git a/guides/multitenancy/assets/mtx-overview.drawio.svg b/guides/multitenancy/assets/mtx-overview.drawio.svg
new file mode 100644
index 000000000..d1fe48339
--- /dev/null
+++ b/guides/multitenancy/assets/mtx-overview.drawio.svg
@@ -0,0 +1,551 @@
+
\ No newline at end of file
diff --git a/guides/multitenancy/assets/mtx-sidecar.drawio.svg b/guides/multitenancy/assets/mtx-sidecar.drawio.svg
new file mode 100644
index 000000000..d83ac9918
--- /dev/null
+++ b/guides/multitenancy/assets/mtx-sidecar.drawio.svg
@@ -0,0 +1,333 @@
+
\ No newline at end of file
diff --git a/guides/multitenancy/assets/persistence-overview.drawio.svg b/guides/multitenancy/assets/persistence-overview.drawio.svg
new file mode 100644
index 000000000..4389923be
--- /dev/null
+++ b/guides/multitenancy/assets/persistence-overview.drawio.svg
@@ -0,0 +1,180 @@
+
\ No newline at end of file
diff --git a/guides/multitenancy/assets/saas-overview.drawio.svg b/guides/multitenancy/assets/saas-overview.drawio.svg
new file mode 100644
index 000000000..7e6cd1a25
--- /dev/null
+++ b/guides/multitenancy/assets/saas-overview.drawio.svg
@@ -0,0 +1,260 @@
+
\ No newline at end of file
diff --git a/guides/multitenancy/assets/sub-account.png b/guides/multitenancy/assets/sub-account.png
new file mode 100644
index 000000000..4f4a06e17
Binary files /dev/null and b/guides/multitenancy/assets/sub-account.png differ
diff --git a/guides/multitenancy/assets/subscribe-bookshop.png b/guides/multitenancy/assets/subscribe-bookshop.png
new file mode 100644
index 000000000..d9d01a8c0
Binary files /dev/null and b/guides/multitenancy/assets/subscribe-bookshop.png differ
diff --git a/guides/multitenancy/index.md b/guides/multitenancy/index.md
new file mode 100644
index 000000000..b685b972a
--- /dev/null
+++ b/guides/multitenancy/index.md
@@ -0,0 +1,1358 @@
+---
+synopsis: >
+ 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.
+label: Multitenancy
+status: released
+impl-variants: true
+---
+
+# Multitenancy
+
+[[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}
+
+
+
+## Prerequisites
+
+Make sure you have the latest version of `@sap/cds-dk` installed:
+
+```sh
+npm update -g @sap/cds-dk
+```
+
+## Jumpstart with an application
+
+To get a ready-to-use _bookshop_ application you can modify and deploy, run:
+
+::: code-group
+
+```sh [Node.js]
+cds init bookshop --add sample
+cd bookshop
+```
+
+```sh [Java]
+cds init bookshop --java --add tiny-sample
+cd bookshop
+```
+
+:::
+
+## Enable Multitenancy {#enable-multitenancy}
+
+Now, you can run this to enable multitenancy for your CAP application:
+
+```sh
+cds add multitenancy --for production
+```
+
+
+
+::: details See what this adds to your Node.js projectβ¦
+
+1. Adds package `@sap/cds-mtxs` to your project:
+
+ ```jsonc
+ {
+ "dependencies": {
+ "@sap/cds-mtxs": "^1.17"
+ },
+ }
+ ```
+
+2. Adds this configuration to your _package.json_ to enable multitenancy with sidecar:
+
+ ```jsonc
+ {
+ "cds": {
+ "profile": "with-mtx-sidecar",
+ "requires": {
+ "[production]": {
+ "multitenancy": true
+ }
+ }
+ }
+ }
+ ```
+
+3. Adds a sidecar subproject at `mtx/sidecar` with this _package.json_:
+
+ ```json
+ {
+ "name": "bookshop-mtx",
+ "dependencies": {
+ "@sap/cds": "^7",
+ "@sap/cds-hana": "^2",
+ "@sap/cds-mtxs": "^1.17",
+ "@sap/xssec": "^3",
+ "express": "^4"
+ },
+ "devDependencies": {
+ "@cap-js/sqlite": ">=1"
+ },
+ "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β¦
+
+1. Adds the following to _.cdsrc.json_ in your app:
+
+ ```jsonc
+ {
+ "profiles": [
+ "with-mtx-sidecar",
+ "java"
+ ],
+ "requires": {
+ "[production]": {
+ "multitenancy": true
+ }
+ }
+ }
+ ```
+
+2. Adds the following to your _srv/pom.xml_ in your app:
+
+ ```xml
+
+ com.sap.cds
+ cds-feature-mt
+ runtime
+
+ ```
+
+3. Adds the following to your _srv/src/java/resources/application.yaml_:
+
+ ```yml
+ ---
+ spring:
+ config.activate.on-profile: cloud
+ cds:
+ multi-tenancy:
+ mtxs.enabled: true
+
+ ```
+
+1. Adds a sidecar subproject at `mtx/sidecar` with this _package.json_:
+
+ ```json
+ {
+ "name": "bookshop-mtx",
+ "dependencies": {
+ "@sap/cds": ">=7",
+ "@sap/cds-hana": "^2",
+ "@sap/cds-mtxs": "^2",
+ "@sap/xssec": "^4",
+ "express": "^4"
+ },
+ "devDependencies": {
+ "@cap-js/sqlite": "^1"
+ },
+ "scripts": {
+ "start": "cds-serve",
+ "build": "cds build ../.. --for mtx-sidecar --production && npm ci --prefix gen"
+ },
+ "cds": {
+ "profile": "mtx-sidecar"
+ }
+ }
+ ```
+
+:::
+
+
+
+After adding multitenancy, install your application dependencies:
+
+```sh
+npm i
+```
+
+
+
+
+
+After adding multitenancy, Maven build should be used to generate the model related artifacts:
+
+```sh
+mvn install
+```
+
+
+
+## Test-Drive Locally {#test-locally}
+
+For local testing, create a new profile that contains the multitenancy configuration:
+
+```sh
+cds add multitenancy --for local-multitenancy
+```
+
+
+
+ For multitenancy 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
+
+ com.sap.cds
+ cds-starter-cloudfoundry
+
+ ```
+
+ Then you add additional mock users to the spring-boot profile:
+
+ ::: code-group
+
+ ```yaml [application.yaml]
+ ---
+ spring:
+ config.activate.on-profile: local-multitenancy
+ #...
+ cds:
+ multi-tenancy:
+ mtxs.enabled: true
+ security.mock.users: // [!code focus]
+ - name: alice // [!code focus]
+ tenant: t1
+ roles: [ admin ]
+ - name: bob // [!code focus]
+ tenant: t1
+ roles: [ cds.ExtensionDeveloper ]
+ - name: erin // [!code focus]
+ tenant: t2
+ roles: [ admin, cds.ExtensionDeveloper ]
+ ```
+
+ :::
+
+Configure the sidecar to use dummy authentication.
+
+::: code-group
+
+```json [mtx/sidecar/package.json]
+{
+ "cds": {
+ "profile": "mtx-sidecar",
+ "[development]": {
+ "requires": {
+ "auth": "dummy"
+ }
+ }
+ }
+}
+```
+
+:::
+
+
+
+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 Using multiple terminalsβ¦
+In the following steps, we start two servers, the main app and MTX sidecar, and execute some CLI commands. So, you need three terminal windows.
+:::
+
+### 1. Start MTX Sidecar
+
+ ```sh
+ cds watch mtx/sidecar
+ ```
+
+ ::: details Trace output explained
+
+ 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 being deployed, which is used by the MTX services for book-keeping tasks.
+
+ ```log
+ [cds|t0] - 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/troubleshooting#why-do-i-get-an-error-on-server-start){.learn-more}
+
+### 2. Launch App Server
+
+
+
+ ```sh
+ cds watch --profile local-multitenancy
+ ```
+
+ ::: details Persistent database
+
+ The server starts as usual, but automatically uses a persistent database instead of an in-memory one:
+
+ ```log
+ [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 ]
+ ```
+
+ :::
+
+
+
+
+
+ ```sh
+ cd srv
+ mvn cds:watch -Dspring-boot.run.profiles=local-multitenancy
+ ```
+
+ ::: details Persistent database
+
+ The server starts as usual, with the difference that a persistent database is used automatically instead of an in-memory one:
+
+ ```log
+ 2023-03-31 14:19:23.987 INFO 68528 --- [ restartedMain] c.s.c.bookshop.Application : The following 1 profile is active: "local-mtxs"
+ ...
+ 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 to two tenants using one of the following methods.
+
+ ::: code-group
+
+ ```sh [CLI]
+ cds subscribe t1 --to http://localhost:4005 -u yves:
+ cds subscribe t2 --to http://localhost:4005 -u yves:
+ ```
+
+ ```http
+ POST http://localhost:4005/-/cds/deployment/subscribe 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.subscribe('t1')
+ ```
+
+ :::
+
+ > Run `cds help subscribe` to see all available options.
+
+
+
+ ::: details `cds subscribe` explained
+
+ 1. Be reminded that these commands are only relevant for local testing. For a deployed app, [subscribe to your tenants](#subscribe) through the BTP cockpit.
+
+ 2. In the CLI commands, we use the pre-defined mock user `yves`, see [pre-defined mock users](../../node.js/authentication#mock-users).
+
+ 3. The subscription is sent to the MTX sidecar process (listening on port **4005**)
+
+ 4. The sidecar reacts with trace outputs like this:
+
+ ```log
+ [cds] - POST /-/cds/deployment/subscribe
+ ...
+ [mtx] - successfully subscribed tenant t1 // [!code focus]
+ ```
+
+ 5. In response to each subscription, the sidecar creates a new persistent tenant database per tenant, 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
+ ```
+
+ 6. To unsubscribe a tenant, run:
+
+ ```sh
+ cds unsubscribe βΉtenantβΊ --from http://localhost:4005 -u βΉuserβΊ
+ ```
+
+ > Run `cds help unsubscribe` to see all available options.
+ :::
+
+#### Test with Different Users/Tenants {.node}
+
+Open the _Manage Books_ app at 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.
+
+{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 in it → 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`:
+
+ ```console
+ [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]
+ }
+ }
+ }
+ }
+ ```
+
+ :::
+
+### 4. 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')
+ ```
+
+ :::
+
+
+
+
+
+Now, open or refresh again as _alice_ and _erin_ → the added entries are visible for _alice_, but still missing for _erin_, as `t2` has not yet been upgraded.
+
+
+
+## Deploy to Cloud
+
+### Cloud Foundry / Kyma
+
+In order to get your multitenant application deployed, follow this excerpt from the [deployment to CF](../deployment/to-cf) and [deployment to Kyma](../deployment/to-kyma) guides.
+
+Once: Add SAP HANA Cloud, XSUAA, and [App Router](../deployment/to-cf#add-app-router) configuration. The App Router acts as a single point-of-entry gateway to route requests to. In particular, it ensures user login and authentication in combination with XSUAA.
+
+```sh
+cds add hana,xsuaa,approuter --for production
+```
+
+If you intend to serve UIs you can easily set up the SAP Cloud Portal service:
+
+```sh
+cds add portal
+```
+
+Once: add a **deployment descriptor**:
+
+::: code-group
+
+```sh [Cloud Foundry]
+cds add mta
+```
+
+```sh [Kyma]
+cds add helm,containerize
+```
+
+:::
+
+::: details Add xsuaa redirect for trial / extension landscapes
+Add the following snippet to your _xs-security.json_ and adapt it to the landscape you're deploying to:
+
+```json
+ "oauth2-configuration": {
+ "redirect-uris": ["https://*.cfapps.us10-001.hana.ondemand.com/**"]
+ }
+```
+
+:::
+
+[Learn more about configured BTP services for SaaS applications.](#behind-the-scenes){.learn-more}
+
+[Freeze the `npm` dependencies](../deployment/to-cf#freeze-dependencies) for server and MTX sidecar.
+
+```sh
+npm update --package-lock-only
+npm update --package-lock-only --prefix mtx/sidecar
+```
+
+In addition, you need install and freeze dependencies for your UI applications:
+```sh
+npm i --prefix app/browse
+npm i --prefix app/admin-books
+```
+
+**Build and deploy**:
+
+::: code-group
+
+```sh [Cloud Foundry]
+mbt build -t gen --mtar mta.tar
+cf deploy gen/mta.tar
+```
+
+```sh [Kyma]
+# Omit `--push` flag for testing, otherwise `ctz`
+# will push images to the specified repository
+ctz containerize.yaml --push
+helm upgrade --install bookshop ./chart
+```
+
+:::
+
+### Subscribe
+
+**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}
+
+{.mute-dark}
+
+In your **subscriber account** go to _Instances and Subscription_ and select _Create_.
+
+{.mute-dark}
+
+Select _bookshop_ and use the only available plan _default_.
+
+{.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_.
+
+{.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_.
+
+{.mute-dark}
+
+Here, you need to enter a _Domain_ and _Host Name_.
+
+{.mute-dark}
+
+Let's use this route as example:
+
+__
+
+- 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.
+
+{.mute-dark}
+
+Click on _Map Route_, choose your App Router module and hit _Save_.
+
+{.mute-dark}
+
+You should now see the route mapped to your application.
+
+{.mute-dark}
+
+:::
+
+### Update Database Schema
+
+
+
+There are several ways to run the update of the database schema.
+
+#### MTX Sidecar API
+
+Please check the [Upgrade API](./mtxs#upgrade-tenants-β-jobs) to see how the database schema update can be run for single or all tenants using the API endpoint.
+
+#### `cds-mtx upgrade` Command
+
+The database schema upgrade can also be run using `cds-mtx upgrade `. The command must be run in the MTX sidecar root directory.
+
+##### Run as Cloud Foundry hook
+
+Example definition for a [module hook](https://help.sap.com/docs/btp/sap-business-technology-platform/module-hooks):
+
+```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 '*'
+```
+
+[Blue-green deployment strategy for MTAs](https://help.sap.com/docs/btp/sap-business-technology-platform/blue-green-deployment-strategy){.learn-more}
+
+##### Manually run as Cloud Foundry Task
+
+You can also invoke the command manually using `cf run-task`:
+
+```sh
+cf run-task --name "upgrade-all" --command "cds-mtx upgrade '*'"
+```
+
+
+
+### Test-Drive with Hybrid Setup
+
+For faster turnaround cycles in development and testing, you can run the app locally while binding it to remote service instances created by a Cloud Foundry deployment.
+
+To achieve this, bind your SaaS app and the MTX sidecar to its required cloud services, for example:
+
+```sh
+cds bind --to-app-services bookshop-srv
+```
+
+For testing the sidecar, make sure to run the command there as well:
+
+```sh
+cd mtx/sidecar
+cds bind --to-app-services bookshop-srv
+```
+
+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-locally). Run this in your project root:
+
+```sh
+cds watch mtx/sidecar --profile hybrid
+```
+
+And in another terminal:
+
+
+
+Learn more about [Hybrid Testing](../../advanced/hybrid-testing).{.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
+```
+
+:::
+
+## SaaS Registry Dependencies {#saas-dependencies}
+
+Some of the services your application consumes need to be registered as _reuse services_ to work in multitenant environments. `@sap/cds-mtxs` offers an easy way to integrate these dependencies. It supports some services out of the box and also provides a simple API for plugins.
+
+Most notably, you will need such dependencies for the SAP BTP [Audit Log](https://discovery-center.cloud.sap/serviceCatalog/audit-log-service), [Connectivity](https://discovery-center.cloud.sap/serviceCatalog/connectivity-service), [Destination](https://discovery-center.cloud.sap/serviceCatalog/destination), [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) services. 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 above activate the default configuration in `@sap/cds-mtxs`:
+
+```json
+"cds": {
+ "requires": {
+ "audit-log": {
+ // Uses credentials.uaa.xsappname
+ "subscriptionDependency": { "uaa": "xsappname" }
+ },
+ "connectivity": {
+ // Uses credentials.xsappname
+ "subscriptionDependency": "xsappname"
+ },
+ ...
+ }
+}
+```
+
+:::
+
+::: details If you need additional services...
+
+You can use the `subscriptionDependency` setting to provide a similar dependency configuration in your application or CAP plugin _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 example for `audit-log` above.
+
+Alternatively, overriding the [`dependencies`](./mtxs#get-dependencies) handler gives you full flexibility for any custom implementation.
+
+:::
+
+## Add Custom Handlers
+
+MTX services are implemented as standard CAP services, so you can register for events just as you would for any application service.
+
+### In the Java Main Project {.java}
+
+For Java, you can add custom handlers to the main app as described in the [documentation](/java/multitenancy#custom-logic):
+
+```java
+@After
+private void subscribeToService(SubscribeEventContext context) {
+ String tenant = context.getTenant();
+ Map options = context.getOptions();
+}
+
+@On
+private void upgradeService(UpgradeEventContext context) {
+ List tenants = context.getTenants();
+ Map options = context.getOptions();
+}
+
+@Before
+private void unsubscribeFromService(UnsubscribeEventContext context) {
+ String tenant = context.getTenant();
+ Map options = context.getOptions();
+}
+```
+
+### In the Sidecar Subproject
+
+You can add custom handlers in the sidecar project, implemented in Node.js.
+
+```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
+ })
+})
+
+```
+
+
+## Appendix
+
+### Behind the Scenes { #behind-the-scenes}
+
+With adding the MTX services, your project configuration is adapted at all relevant places.
+
+Configuration and dependencies are added to your _package.json_ and an _xs-security.json_ containing MTX-specific scopes and roles is created. {.node}
+
+Configuration and dependencies are added to your _.cdsrc.json_ and an _xs-security.json_ containing MTX-specific scopes and roles is created. {.java}
+
+For the MTA deployment service dependencies are added to the _mta.yaml_ file. Each SaaS application will have bindings to at least three SAP BTP service instances.
+
+| Service | Description |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| [Service Manager](https://help.sap.com/docs/SERVICEMANAGEMENT/09cc82baadc542a688176dce601398de/4e19b11211fe4ca2a266d3fdd4a72188.html) (`service-manager`) | CAP uses this service for creating a new SAP HANA Deployment Infrastructure (HDI) container for each tenant and for retrieving tenant-specific database connections. |
+| [SaaS Provisioning Service](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/3971151ba22e4faa9b245943feecea54.html) (`saas-registry`) | To make a SaaS application available for subscription to SaaS consumer tenants, the application provider must register the application in the SAP BTP Cloud Foundry environment through the SaaS Provisioning Service. |
+| [User Account and Authentication Service](https://help.sap.com/docs/CP_AUTHORIZ_TRUST_MNG) (`xsuaa`) | Binding information contains the OAuth client ID and client credentials. The XSUAA service can be used to validate the JSON Web Token (JWT) from requests and to retrieve the tenant context from the JWT.|
+
+> If you're interested, use version control to spot the exact changes. {.node}
+
+#### Configuring the Java Service { #binding-it-together .java}
+
+This how the services have been configured for the `srv` module in the _mta.yaml_ / _values.yaml_ file:
+
+::: code-group
+
+```yaml [mta.yaml]
+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]
+...
+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 }} #cds.noOverwrite
+ ...
+```
+:::
+
+> These environment variables in `values.yaml` may be overwritten by `cds add` commands. If you want to provide your own value and don't want `cds add` commands to overwrite the value of any particular variable, add `#cds.noOverwrite` comment next to that value (as shown in above example).
+
+- `CDS_MULTITENANCY_SIDECAR_URL` sets the application property cds.multitenancy.sidecar.url. This URL is required by the CAP Java runtime to connect to the MTX Sidecar application and is derived from the property `url` of the mtx-sidecar module.
+- Similarly, `CDS_MULTITENANCY_APPUI_URL` configures the URL that is shown in the SAP BTP Cockpit. Usually it points to the app providing the UI, which is the module `app` in this example.
+
+The tenant application requests are separated by the tenant specific app URLs. The tenant specific URL is made up of:
+
+```http
+https://
+```
+
+You can also define the environment variable `CDS_MULTITENANCY_APPUI_TENANTSEPARATOR` in the extension descriptor. The extension descriptor file could look like this:
+
+::: 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 { #provisioning-only-mtx-sidecar .java}
+
+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.
+
+:::
+
+### 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](../deployment/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.
+
+
+If everything is set up, the following graphic shows what's happening when a user subscribes to a SaaS application:
+
+{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.
+
+
+
+In CAP Java, tenant provisioning is delegated to CAP Node.js based services. This has the following implications:
+
+- Java applications need to run and maintain the [_cds-mtxs_ module](../multitenancy/#enable-multitenancy) as a sidecar application (called _MTX sidecar_ in this documentation).
+- But multitenant CAP Java applications automatically expose the tenant provisioning API called by the SaaS Provisioning service so that [custom logic during tenant provisioning](/java/multitenancy#custom-logic) can be written in Java.
+
+
+
+### 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 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.
+
+
+
+
+
+
+
+## Next Steps
+
+- See the [MTX Services Reference](./mtxs) for details on service and configuration options, in particular about sidecar setups.
+- See our guide on [Extending and Customizing SaaS Solutions](../extensibility/).
diff --git a/guides/multitenancy/mtxs.md b/guides/multitenancy/mtxs.md
new file mode 100644
index 000000000..d2831958c
--- /dev/null
+++ b/guides/multitenancy/mtxs.md
@@ -0,0 +1,1384 @@
+---
+
+label: MTX Reference
+# layout: reference-doc
+breadcrumbs:
+ - Cookbook
+ - Multitenancy
+ - MTX Reference
+status: released
+---
+
+# MTX Services Reference
+
+{{$frontmatter?.synopsis}}
+
+
+
+## Introduction & Overview
+
+The `@sap/cds-mtxs` package provides a set of CAP services which implement _**multitenancy**_, _[features toggles](../extensibility/feature-toggles)_ and _[extensibility](../extensibility/)_ (_'MTX'_ stands for these three functionalities). These services work in concert as depicted in the following diagram:
+
+
+
+MTX services are implemented in Node.js and can run in the same Node.js server as your application services or in separate micro services called _sidecars_. All services can be consumed via REST APIs.
+
+As the services are defined and implemented as standard CAP services, with definitions in CDS and implementations based on the CAP Node.js framework, application projects can hook into all events to add custom logic using CAP Node.js.
+
+## Getting Startedβ¦
+
+### Add `@sap/cds-mtxs` Package Dependency
+
+```sh
+npm add @sap/cds-mtxs
+```
+
+### Enable MTX Functionality
+
+Add one or more of the following convenience configuration flags, for example, to your `package.json` in a Node.js-based project:
+
+```json
+ "cds": {
+ "requires": {
+ "multitenancy": true,
+ "extensibility": true,
+ "toggles": true
+ }
+ }
+```
+
+[Java-based projects require a sidecar setup.](#sidecars){.learn-more}
+
+### Test-Drive Locally
+
+After enabling MTX features, you can test MTX functionality with local development setups and in-memory databases as usual:
+
+```sh
+cds watch
+```
+
+This shows the MTX services being served in addition to your app services:
+
+```log{6-8,11-15}
+[cds] - loaded model from 6 file(s):
+
+ db/schema.cds
+ srv/admin-service.cds
+ srv/cat-service.cds
+ ../../db/extensions.cds
+ ../../srv/deployment-service.cds
+ ../../srv/bootstrap.cds
+
+[cds] - connect to db > sqlite { url: ':memory:' }
+[cds] - serving cds.xt.SaasProvisioningService { path: '/-/cds/saas-provisioning' }
+[cds] - serving cds.xt.DeploymentService { path: '/-/cds/deployment' }
+[cds] - serving cds.xt.ModelProviderService { path: '/-/cds/model-provider' }
+[cds] - serving cds.xt.ExtensibilityService { path: '/-/cds/extensibility' }
+[cds] - serving cds.xt.JobsService { path: '/-/cds/jobs' }
+[cds] - serving AdminService { path: '/admin' }
+[cds] - serving CatalogService { path: '/browse', impl: 'srv/cat-service.js' }
+
+[cds] - server listening on { url: 'http://localhost:4004' }
+[cds] - launched at 5/6/2023, 9:31:11 AM, in: 863.803ms
+```
+
+
+## Grow As You Go
+
+Follow CAP principles of _'Grow as you go...'_ to minimize complexity of setups, stay in [inner loops](https://www.getambassador.io/docs/telepresence/latest/concepts/devloop) with fast turnarounds, and hence minimize costs and accelerate development.
+
+### Enable MTX Only if Required
+
+During development you rarely need to run your servers with MTX functionality enabled. Only do so when you really need it. For example, in certain tests or by using configuration profiles.
+
+ This configuration would have development not use MTX by default. You could still run with MTX enabled on demand and have it always active in production:
+
+
+```jsonc
+ "cds": {
+ "requires": {
+ "[local-multitenancy]": {
+ "multitenancy": true,
+ "extensibility": true,
+ "toggles": true
+ },
+ "[production]": {
+ "multitenancy": true,
+ "extensibility": true,
+ "toggles": true
+ }
+ }
+ }
+```
+
+During development you could occasionally run with MTX:
+
+```sh
+cds watch --profile local-multitenancy
+```
+
+### Testing With Minimal Setup
+
+When designing test suites that run frequently in CI/CD pipelines, you can shorten runtimes and reduce costs. First run a set of functional tests which use MTX in minimized setups β that is, with local servers and in-memory databases as introduced in the [_Multitenancy_ guide](../multitenancy/#test-locally).
+
+Only in the second and third phases, you would then run the more advanced hybrid tests. These hybrid tests could include testing tenant subscriptions with SAP HANA, or integration tests with the full set of required cloud services.
+
+## Sidecar Setups {#sidecars}
+
+In the minimal setup introduced in the _[Getting Started...](#getting-started)_ chapter, we had the MTX services being served embedded with our main app, that is, in the same server as our application services. While this is possible for Node.js and even recommended to reduce complexity during development, quite frequently, we'd want to run them in a separate micro service. Reasons for that include:
+
+- **For Java-based projects** β As these services are implemented in Node.js we need to run them separately and consume them remotely for Java-based apps.
+- **To scale independently** β As some operations, especially `upgrade`, are very resource-intensive, we want to scale these services separate from our main application.
+
+As MTX services are built and consumed as CAP services, we benefit from CAP's agnostic design and can easily move them to separate services.
+
+### Create Sidecar as a Node.js Subproject
+
+An MTX sidecar is a standard, yet minimal Node.js CAP project. By default it's added to a subfolder `mtx/sidecar` within your main project, containing just a _package.json_ file.
+
+::: code-group
+
+```json [mtx/sidecar/package.json]
+{
+ "name": "bookshop-mtx", "version": "0.0.0",
+ "dependencies": {
+ "@sap/cds": "^7",
+ "@sap/cds-hana": "^2",
+ "@sap/cds-mtxs": "^1",
+ "@sap/xssec": "^3",
+ "express": "^4"
+ },
+ "devDependencies": {
+ "@cap-js/sqlite": "^1"
+ },
+ "scripts": {
+ "start": "cds-serve"
+ },
+ "cds": {
+ "profile": "mtx-sidecar"
+ }
+}
+```
+
+:::
+
+The only configuration necessary for the project is the `mtx-sidecar` profile.
+
+::: details Let's have a look at what this profile provides...
+
+#### Required MTX Services
+
+```jsonc
+...
+"cds": {
+ "requires": {
+ "cds.xt.ModelProviderService": "in-sidecar",
+ "cds.xt.DeploymentService": true,
+ "cds.xt.SaasProvisioningService": true,
+ "cds.xt.ExtensibilityService": true
+ ...
+ }
+}
+```
+
+Here we enable all MTX services in a standard configuration. Of course, you can choose to only serve some of which, according to your needs, using [individual configuration](#conf-individual).
+
+#### Using Shared Database
+
+```jsonc
+...
+"[development]": {
+ "db": { "kind": "sqlite", "credentials": {
+ "url": "../../db.sqlite"
+ }}
+}
+...
+```
+
+With multitenancy the _[DeploymentService](#deploymentservice)_ needs to deploy the very database instances which are subsequently used by the main application. This setting ensures that for local development with SQLite.
+
+#### Additional `[development]` Settings
+
+```jsonc
+...
+"[development]": {
+ "requires": { "auth": "mocked" },
+ "server": { "port": 4005 }
+}
+...
+```
+
+These additional settings for profile `[development]` are to support local tests with default values for the server port (different from the default port `4004` of the main app), and to allow mock authentication in the sidecar (secured by default in production).
+
+:::
+
+### Testing Sidecar Setups
+
+With the above setup in place, we can test-drive the sidecar mode locally. To do so, we'll simply start the sidecar and main app in separate shells.
+
+1. Run sidecar in first shell:
+
+ ```sh
+ cds watch mtx/sidecar
+ ```
+
+ ::: details You see the sidecar starting on port 4005...
+
+ ```log {28}
+ cd mtx/sidecar
+
+ cds serve all --with-mocks --in-memory?
+ live reload enabled for browsers
+
+ ___________________________
+
+ [cds] - loaded model from 3 file(s):
+
+ ../cds-mtxs/srv/model-provider.cds
+ ../cds-mtxs/srv/deployment-service.cds
+ ../cds-mtxs/db/t0.cds
+
+ [cds] - connect using bindings from: { registry: '~/.cds-services.json' }
+ [cds] - connect to db > sqlite { url: '../../db.sqlite' }
+ [cds] - using authentication: { kind: 'mocked' }
+
+ [cds] - serving cds.xt.ModelProviderService { path: '/-/cds/model-provider' }
+ [cds] - serving cds.xt.DeploymentService { path: '/-/cds/deployment' }
+ [cds] - loaded model from 1 file(s):
+
+ ../cds-mtxs/db/t0.cds
+
+ [mtx] - (re-)deploying SQLite database for tenant: t0
+ /> successfully deployed to db-t0.sqlite
+
+
+ [cds] - server listening on { url: 'http://localhost:4005' }
+ [cds] - launched at 5/6/2023, 1:08:33 AM, version: 7.3.0, in: 772.25ms
+ [cds] - [ terminate with ^C ]
+ ```
+
+ :::
+
+2. Run the main app as before in a second shell:
+
+ ```sh
+ cds watch
+ ```
+
+#### _ModelProviderService_ serving models from main app
+
+When we use our application, we can see `model-provider/getCsn` requests in the sidecar's trace log. In response to those requests, the sidecar reads and returns the main app's models, that is, the models from two levels up the folder hierarchy as is the default with the `mtx-sidecar` profile.
+
+#### Note: Service Bindings by `cds watch`
+
+Required service bindings are done automatically by `cds watch`'s built-in runtime service registry. This is how it works:
+
+1. Each server started using `cds watch` registers all served services in `~/cds-services.json`.
+
+2. Every subsequently started server binds automatically all `required` remote services, to equally named services already registered in `~/cds-services.json`.
+
+In our case: The main app's `ModelProviderService` automatically receives the service binding credentials, for example `url`, to talk to the one served by the sidecar.
+
+### Build Sidecar for Production
+
+When deploying a sidecar for production, it doesn't have access to the main app's models two levels up the deployed folder hierarchy. Instead we have to prepare deployment by running `cds build` in the project's root:
+
+```sh
+cds build
+```
+
+One of the build tasks that are executed is the `mtx-sidecar` build task. It generates log output similar to the following:
+
+```log
+[cds] - the following build tasks will be executed
+ {"for":"mtx-sidecar", "src":"mtx/sidecar", "options":... }
+[cds] - done > wrote output to:
+ gen/mtx/sidecar/_main/fts/isbn/csn.json
+ gen/mtx/sidecar/_main/fts/reviews/csn.json
+ gen/mtx/sidecar/_main/resources.tgz
+ gen/mtx/sidecar/_main/srv/_i18n/i18n.json
+ gen/mtx/sidecar/_main/srv/csn.json
+ gen/mtx/sidecar/package.json
+ gen/mtx/sidecar/srv/_i18n/i18n.json
+ gen/mtx/sidecar/srv/csn.json
+[cds] - build completed in 687 ms
+```
+
+The outcome of that build task is a compiled and deployable version of the sidecar in the _gen/mtx/sidecar_ staging areas:
+
+```zsh{6-17}
+bookshop/
+ββ _i18n/
+ββ app/
+ββ db/
+ββ fts/
+ββ gen/mtx/sidecar/
+β ββ _main/
+β β βββ fts/
+β β β βββ isbn/
+β β β β βββ csn.json
+β β β βββ reviews/
+β β β βββ csn.json
+β β βββ srv/
+β β β βββ _i18n
+β β β βββ csn.json
+β β βββ resources.tgz
+β ββ package.json
+ββ mtx/sidecar/
+ββ ...
+```
+
+In essence, the `mtx-sidecar` build task does the following:
+
+1. It runs a standard Node.js build for the sidecar.
+2. It pre-compiles the main app's models, including all features into respective _csn.json_ files, packaged into the `_main` subfolder.
+3. It collects all additional sources required for subsequent deployments to `resources.tgz`. For example, these include _.csv_ and _i18n_ files.
+
+### Test-Drive Production Locally
+
+We can also test-drive the production-ready variant of the sidecar locally before actual deployment, again using two separate shells.
+
+1. **First, start sidecar** from `gen/mtx/sidecar` in `prod` simulation mode:
+
+ ```sh
+ cds watch gen/mtx/sidecar --profile development,prod
+ ```
+
+2. **Second, start main** app as usual:
+
+ ```sh
+ cds watch
+ ```
+
+#### _ModelProviderService_ serving models from main app
+
+When we now use our application again, and inspect the sidecar's trace logs, we see that the sidecar reads and returns the main app's precompiled models from `_main` now:
+
+```log
+[cds] β POST /-/cds/model-provider/getCsn
+[cds] β model loaded from 3 file(s):
+
+ gen/mtx/sidecar/_main/srv/csn.json
+ gen/mtx/sidecar/_main/fts/isbn/csn.json
+ gen/mtx/sidecar/_main/fts/reviews/csn.json
+```
+
+## Configuration {#conf}
+
+### Shortcuts `cds.requires.multitenancy / extensibility / toggles` {#conf-shortcuts}
+
+The easiest way to enable multitenancy, extensibility, and feature toggles is as follows:
+
+```json
+ "cds": {
+ "requires": {
+ "multitenancy": true,
+ "extensibility": true,
+ "toggles": true
+ }
+ }
+```
+
+On the one hand, these settings are interpreted by the CAP runtime to support features such as tenant-specific database connection pooling when `multitenancy` is enabled.
+
+On the other hand, these flags are checked during server bootstrapping to ensure the required combinations of services are served by default. The following tables shows which services are enabled by one of the shortcuts:
+
+| | `multitenancy` | `extensibility` | `toggles` |
+| ----------------------------------------------------- | :------------: | :-------------: | :-------: |
+| _[SaasProvisioningService](#saasprovisioningservice)_ | yes | no | no |
+| _[DeploymentService](#deploymentservice)_ | yes | no | no |
+| _[ExtensibilityService](#extensibilityservice)_ | no | yes | no |
+| _[ModelProviderService](#modelproviderservice)_ | yes | yes | yes |
+
+### Configuring Individual Services {#conf-individual}
+
+In addition or alternatively to the convenient shortcuts above you can configure each service individually, as shown in the following examples:
+
+```jsonc
+ "cds": {
+ "requires": {
+ "cds.xt.DeploymentService": true
+ }
+ }
+```
+
+The names of the service-individual configuration options are:
+
+- `cds/requires/`
+
+##### Allowed Values
+
+- `false` β deactivates the service selectively
+- `true` β activates the service with defaults for embedded usage
+- `` β uses [preset](#presets), for example, with defaults for sidecar usage
+- `{ ...options }` β add/override individual configuration options
+
+##### Common Config Options
+
+- `model` β specifies/overrides the service model to be used
+- `impl` β specifies/overrides the service implementation to be used
+- `kind` β the kind of service/consumption, for example, `rest` for remote usage
+
+> These options are supported by all services.
+
+#### Combined with Convenience Flags
+
+```json
+ "cds": {
+ "requires": {
+ "multitenancy": true,
+ "cds.xt.SaasProvisioningService": false,
+ "cds.xt.DeploymentService": false,
+ "cds.xt.ModelProviderService": { "kind": "rest" }
+ }
+ }
+```
+
+This tells the CAP runtime to enable multitenancy, but neither serve the _DeploymentService_, nor the _SaasProvisioningService_, and to use a remote _ModelProviderService_ via REST protocol.
+
+#### Individual Configurations Only
+
+We can also use only the individual service configurations:
+
+```json
+ "cds": {
+ "requires": {
+ "cds.xt.DeploymentService": true,
+ "cds.xt.ModelProviderService": { "root": "../.." }
+ }
+ }
+```
+
+In this case, the server will **not** run in multitenancy mode. Also, extensibility and feature toggles are not supported. Yet, the _DeploymentService_ and the _ModelProviderService_ are served selectively. For example, this kind of configuration can be used in [sidecars](#sidecars).
+
+### Using Configuration Presets {#presets}
+
+#### Profile-based configuration
+
+The simplest and for most projects sufficient configuration is the profile-based one, where just these two entries are necessary:
+
+::: code-group
+
+```json [package.json]
+"cds": {
+ "profile": "with-mtx-sidecar"
+}
+```
+
+:::
+
+::: code-group
+
+```json [mtx/sidecar/package.json]
+"cds": {
+ "profile": "mtx-sidecar"
+}
+```
+
+:::
+
+
+#### Preset-based configuration
+
+Some MTX services come with pre-defined configuration presets, which can easily be used by referring to the preset suffixes. For example, to simplify and standardize sidecar configuration, _[ModelProviderService](#modelproviderservice)_ supports the `in-sidecar` preset which can be used like that:
+
+```json
+ "cds": {
+ "requires": {
+ "cds.xt.ModelProviderService": "in-sidecar"
+ }
+ }
+```
+
+These presets are actually configured in `cds.env` defaults like that:
+
+```js
+cds: {
+ requires: {
+ // Configuration Presets (in cds.env.requires.kinds)
+ kinds: {
+ "cds.xt.ModelProviderService-in-sidecar": {
+ "[development]": { root: "../.." },
+ "[production]": { root: "_main" },
+ },
+ "cds.xt.ModelProviderService": {
+ model: "@sap/cds/srv/model-provider"
+ },
+ // ...
+ }
+ }
+}
+```
+
+[Learn more about `cds.env`](../../node.js/cds-env){.learn-more}
+
+### Inspecting Effective Configuration
+
+You can always inspect the effective configuration by executing this in the _mtx/sidecar_ folder:
+
+```sh
+cds env get requires
+```
+
+This will give you an output like this:
+
+```js
+{
+ auth: { strategy: 'dummy', kind: 'dummy' },
+ 'cds.xt.ModelProviderService': {
+ root:'../..',
+ model:'@sap/cds/srv/model-provider',
+ kind:'in-sidecar'
+ }
+}
+```
+
+Add CLI option `--profile` to inspect configuration in different profiles:
+
+```sh
+cds env get requires --profile development
+cds env get requires --profile production
+```
+
+## Customization
+
+All services are defined and implemented as standard CAP services, with service definitions in CDS, and implementations based on the CAP Node.js framework. Thus, you can easily do both, adapt service definitions, as well as hook into all events to add custom logic using CAP Node.js.
+
+### Customizing Service Definitions
+
+For example, you could override the endpoints to serve a service:
+
+```cds
+using { cds.xt.ModelProviderService } from '@sap/cds-mtxs';
+annotate ModelProviderService with @path: '/mtx/mps';
+```
+
+For sidecar scenarios, define the annotations in the Node.js sidecar application and not as part of the main application.
+
+### Adding Custom Lifecycle Event Handlers
+
+Register handlers in `server.js` files:
+
+::: code-group
+
+```js [mtx/sidecar/server.js]
+const cds = require('@sap/cds')
+cds.on('served', ()=>{
+ const { 'cds.xt.ModelProviderService': mps } = cds.services
+ const { 'cds.xt.DeploymentService': ds } = cds.services
+ ds.before ('upgrade', (req) => { ... })
+ ds.after ('subscribe', (_,req) => { ... })
+ mps.after ('getCsn', (csn) => { ... })
+})
+```
+
+:::
+
+::: tip Custom hooks for CLI usage
+For CLI usage via `cds subscribe|upgrade|unsubscribe` you can create a `mtx/sidecar/cli.js` file, which works analogously to a `server.js`.
+:::
+
+## Consumption
+
+### Via Programmatic APIs
+
+Consume MTX services using standard [Service APIs](../../node.js/core-services). For example, in `cds repl`:
+
+```js
+await cds.test()
+var { 'cds.xt.ModelProviderService': mps } = cds.services
+var { 'cds.xt.DeploymentService': ds } = cds.services
+var db = await ds.subscribe ('t1')
+var csn = await mps.getCsn('t1')
+cds.context = { tenant:'t1' }
+await db.run('SELECT type, name from sqlite_master')
+```
+
+### Via REST APIs
+
+Common usage of the MTX services is through REST APIs. Here's an example:
+
+1. Start the server
+
+ ```sh
+ cds watch
+ ```
+
+2. Subscribe a tenant
+
+ ```http
+ POST /-/cds/deployment/subscribe HTTP/1.1
+ Content-Type: application/json
+
+ {
+ "tenant": "t1"
+ }
+ ```
+
+3. Get CSN from `ModelProviderService`
+
+ ```http
+ POST /-/cds/model-provider/getCsn HTTP/1.1
+ Content-Type: application/json
+
+ {
+ "tenant": "t1",
+ "toggles": ["*"]
+ }
+ ```
+
+## ModelProviderService
+
+The _ModelProviderService_ serves model variants, which may include tenant-specific extensions and/or feature-toggled aspects.
+
+| | |
+| ----------------------- | ---------------------------------- |
+| Service Definition | `@sap/cds-mtxs/srv/model-provider` |
+| Service Definition Name | `cds.xt.ModelProviderService` |
+| Default HTTP Endpoint | `/-/cds/model-provider` |
+
+### Configuration {#model-provider-config}
+
+```json
+"cds.xt.ModelProviderService": {
+ "root": "../../custom/path"
+}
+```
+
+- [Common Config Options](#common-config-options)
+- `root` β a directory name, absolute or relative to the _package.json_'s location, specifying the location to search for models and resources to be served by the model provider services. Default is undefined, for embedded usage of model provider. In case of a sidecar, it refers to the main app's model; usually `"../.."` during development, and `"_main"` in production.
+
+##### Supported Presets {#model-provider-presets}
+
+- `in-sidecar` β provides defaults for usage in sidecars
+- `from-sidecar` β shortcut for `{ "kind": "rest" }`
+
+### `getCsn` _(tenant, toggles) β CSN_
+
+Returns the application's effective CSN document for the given tenant + feature toggles vector. CAP runtimes call that method to obtain the effective models to serve.
+
+| Arguments | Description |
+| --------- | ----------------------------------------------------------- |
+| `tenant` | A string identifying the tenant |
+| `toggles` | An array listing toggled features; `['*']` for all features |
+
+#### Example Usage {#example-get-csn }
+
+```http
+POST /-/cds/model-provider/getCsn HTTP/1.1
+Content-Type: application/json
+
+{
+ "tenant": "t1",
+ "toggles": ["*"]
+}
+```
+
+The response is a CSN in JSON representation.
+
+[Learn more about **CSN**](http://localhost:5173/docs/cds/csn) {.learn-more}
+
+### `getEdmx` _(tenant, toggles, service, locale) β EDMX_
+
+Returns the EDMX document for a given service in context of the given tenant and feature toggles vector. CAP runtimes call this to get the EDMX document they return in response to OData `$metadata` requests.
+
+| Arguments | Description |
+| --------- | ----------------------------------------------------------- |
+| `tenant` | A string identifying the tenant |
+| `toggles` | An array listing toggled features; `['*']` for all features |
+| `service` | Fully-qualified name of a service definition |
+| `locale` | Requested locale, that is, as from `accept-language` header |
+
+#### Example Usage {#example-get-edmx}
+
+```http
+POST /-/cds/model-provider/getEdmx HTTP/1.1
+Content-Type: application/json
+
+{
+ "tenant": "t1",
+ "toggles": ["*"],
+ "service": "CatalogService",
+ "locale": "en"
+}
+```
+
+### `getResources` _() β TAR_
+
+Returns a _.tar_ archive containing CSV files, I18n files, as well as native database artifacts, required for deployment to databases. `DeploymentService` calls that whenever it receives a `subscribe` or `upgrade` event.
+
+### `getExtensions` _(tenant) β CSN_ {get-extensions}
+
+Returns a _parsed_ CSN document containing all the extensions stored in `cds.xt.Extensions` for the given tenant.
+
+| Arguments | Description |
+| --------- | ------------------------------- |
+| `tenant` | A string identifying the tenant |
+
+### `isExtended` _(tenant) β true|false_
+
+Returns `true` if the given `tenant` has extensions applied.
+
+| Arguments | Description |
+| --------- | ------------------------------- |
+| `tenant` | A string identifying the tenant |
+
+## ExtensibilityService
+
+The _ExtensibilityService_ allows to add and activate tenant-specific extensions at runtime.
+
+| | |
+| ----------------------- | ----------------------------------------- |
+| Service Definition | `@sap/cds-mtxs/srv/extensibility-service` |
+| Service Definition Name | `cds.xt.ExtensibilityService` |
+| Default HTTP Endpoint | `/-/cds/extensibility` |
+
+[See the extensibility guide for more context](../extensibility/customization){.learn-more}
+
+### Configuration {#extensibility-config}
+
+```jsonc
+"cds.xt.ExtensibilityService": {
+ // fields must start with x_ or xx_
+ "element-prefix": ["x_", "xx_"],
+ // namespaces starting with com.sap or sap. can't be extended
+ "namespace-blocklist": ["com.sap.", "sap."],
+ "extension-allowlist": [
+ {
+ // at most 2 new fields in entities from the my.bookshop namespace
+ "for": ["my.bookshop"],
+ "kind": "entity",
+ "new-fields": 2,
+ // allow extensions for field "description" only
+ "fields": ["description"]
+ },
+ {
+ // at most 2 new entities in CatalogService
+ "for": ["CatalogService"],
+ "new-entities": 2,
+ // allow @readonly annotations in CatalogService
+ "annotations": ["@readonly"]
+ }
+ ]
+}
+```
+
+- [Common Config Options](#common-config-options)
+- `element-prefix` β restrict field names to prefix
+- `namespace-blocklist` β restrict namespaces to be extended
+- `extension-allowlist` β allow certain entities to be extended
+
+> Without `extension-allowlist` configured, extensions are forbidden.
+
+Using `"for": ["*"]` applies the rules to all possible values.
+
+See the [list of possible `kind` values](../../cds/csn#def-properties).{.learn-more}
+
+- `new-fields` specifies the maximum number of fields that can be added.
+- `fields` lists the fields that are allowed to be extended. If the list is omitted, all fields can be extended.
+- `new-entities` specifies the maximum number of entities that can be added to a service.
+
+### GET `Extensions/` _β [{ ID, csn, timestamp }]_ {#get-extensions}
+
+Returns a list of all tenant-specific extensions.
+
+
+
+
+
+#### Request Format
+
+| **Parameters** | Description |
+| - | - |
+| `ID` | String uniquely identifying the extension |
+
+> Omitting `ID` will return all extensions.
+
+
+
+#### Request Format
+
+| **Parameters** | Description |
+| - | - |
+| `ID` | String uniquely identifying the extension |
+| **Body**
+| `csn` | Array of extension CDL or CSN to apply |
+| `i18n` | Texts and translations |
+
+
+
+
+
+#### Response Format
+
+| **Body** | Description |
+| - | - |
+| `ID` | String uniquely identifying the extension |
+| `csn` | Compiled extension CSN |
+| `i18n` | Texts and translations |
+| `timestamp` | Timestamp of activation date |
+
+
+
+
+
+#### Example Request
+
+::: code-group
+
+```http [Request]
+PUT /-/cds/extensibility/Extensions/isbn-extension HTTP/1.1
+Content-Type: application/json
+
+{
+ "csn": ["using my.bookshop.Books from '_base/db/data-model';
+ extend my.bookshop.Books with { Z_ISBN: String };"],
+ "i18n": [{ "name": "i18n.properties", "content": "Books_stock=Stock" },
+ { "name": "i18n_de.properties", "content": "Books_stock=Bestand" }]
+}
+```
+
+```json [Response]
+{
+ "ID": "isbn-extension",
+ "csn": "{\"extensions\":[{\"extend\":\"my.bookshop.Books\",\"elements\":{\"Z_ISBN\":{\"type\":\"cds.String\"}}}],\"definitions\":{}}",
+ "i18n": "{\"\":{\"Books_stock\":\"Stock\"},\"de\":{\"Books_stock\":\"Bestand\"}}",
+ "timestamp": "2023-09-07T22:31:28.246Z"
+}
+```
+
+:::
+
+The request can also be triggered asynchronously by setting the `Prefer: respond-async` header.
+You can use the URL returned in the `Location` response header to poll the job status.
+
+In addition, you can poll the status for individual tenants using its individual task ID:
+
+```http
+GET /-/cds/jobs/pollTask(ID='') HTTP/1.1
+```
+
+The response is similar to the following:
+
+```js
+{
+ "status": "FINISHED",
+ "op": "activateExtension"
+}
+```
+
+The job and task status can take on the values `QUEUED`, `RUNNING`, `FINISHED` and `FAILED`.
+
+
+> By convention, custom (tenant-specific) fields are usually prefixed with `Z_`.
+
+The i18n data can also be passed in JSON format:
+```json
+"i18n": [{
+ "name": "i18n.json",
+ "content": "{\"\":{\"Books_stock\":\"Stock\"},\"de\":{\"Books_stock\":\"Bestand\"}}"
+}]
+```
+You also get this JSON in the response body of PUT or [GET](#get-extensions) requests.
+In this example, the text with key "Books_stock" from the base model is replaced.
+
+
+
+
+### DELETE `Extensions/` {#delete-extensions}
+
+Deletes a tenant-specific extension.
+
+#### HTTP Request Options
+
+| Request Header | Example Value | Description |
+| ---------------- | -------------------------------------------------------|--------------|
+| `prefer` | `respond-async` | Trigger asynchronous extension activation |
+
+
+
+
+#### Example Usage
+
+```http [Request]
+DELETE /-/cds/extensibility/Extensions/isbn-extension HTTP/1.1
+Content-Type: application/json
+```
+
+The request can also be triggered asynchronously by setting the `Prefer: respond-async` header.
+You can use the URL returned in the `Location` response header to poll the job status.
+
+In addition, you can poll the status for individual tenants using its individual task ID:
+
+```http
+GET /-/cds/jobs/pollTask(ID='') HTTP/1.1
+```
+
+The response is similar to the following:
+
+```js
+{
+ "status": "FINISHED",
+ "op": "activateExtension"
+}
+```
+
+The job and task status can take on the values `QUEUED`, `RUNNING`, `FINISHED` and `FAILED`.
+
+## DeploymentService
+
+The _DeploymentService_ handles `subscribe`, `unsubscribe`, and `upgrade` events for single tenants and single apps or micro services. Actual implementation is provided through internal plugins, for example, for SAP HANA and SQLite.
+
+| | |
+| ----------------------- | -------------------------------------- |
+| Service Definition | `@sap/cds-mtxs/srv/deployment-service` |
+| Service Definition Name | `cds.xt.DeploymentService` |
+| Default HTTP Endpoint | `/-/cds/deployment` |
+
+### Configuration {#deployment-config}
+
+```jsonc
+"cds.xt.DeploymentService": {
+ "hdi": {
+ "deploy": {
+ ...
+ },
+ "create": {
+ "database_id": "",
+ ...
+ },
+ "bind": {
+ ...
+ }
+ }
+}
+```
+
+- [Common Config Options](#common-config-options)
+- `hdi` β bundles HDI-specific settings
+ - `deploy` β [HDI deployment parameters](https://www.npmjs.com/package/@sap/hdi-deploy#supported-features)
+ - `create` β tenant creation parameters (β [`cf create-service`](https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/a36df26b36484129b482ae20c3eb8004.html))
+ - `database_id` β SAP HANA Cloud instance ID
+ - `bind` β binding parameters (β [`cf bind-service`](https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/c7b09b79d3bb4d348a720ba27fe9a2d5.html))
+
+
+
+##### Supported Presets {#deployment-presets}
+
+- `in-sidecar` β provides defaults for usage in sidecars
+- `from-sidecar` β shortcut for `{ "kind": "rest" }`
+
+### `subscribe` _(tenant)_
+
+Received when a new tenant subscribes.
+
+The implementations create and initialize required resources, that is, creating and initializing tenant-specific HDI containers in case of SAP HANA, or tenant-specific databases in case of SQLite.
+
+### `upgrade` _(tenant)_
+
+Used to upgrade a subscribed tenant.
+
+Implementations read the latest models and content from the latest deployed version of the application and re-deploy that to the tenant's database.
+
+##### Drop-Creating Databases for SQLite
+
+In case of SQLite, especially in case of in-memory databases, an upgrade will simply drop and create a new tenant-specific database. Which means all data is lost.
+
+##### Schema Evolution for SAP HANA
+
+In case of SAP HANA, the delta to the former database layout will be determined, and corresponding CREATE TABLE, DROP-CREATE VIEW, and ALTER TABLE statements will eventually be executed without any data loss.
+
+### `unsubscribe` _(tenant)_
+
+Received when a tenant is deleted.
+
+The implementations free required resources, that is, dispose tenant-specific HDI containers in case of SAP HANA, or tenant-specific databases in case of SQLite.
+
+## SaasProvisioningService
+
+The _SaasProvisioningService_ is a façade for the _DeploymentService_ to adapt to the API expected by [SAP BTP's SaaS Provisioning service](https://discovery-center.cloud.sap/serviceCatalog/saas-provisioning-service), hence providing out-of-the-box integration.
+
+| | |
+| ----------------------- | ------------------------------------------------ |
+| Service Definition | `@sap/cds-mtxs/srv/cf/saas-provisioning-service` |
+| Service Definition Name | `cds.xt.SaasProvisioningService` |
+| Default HTTP Endpoint | `/-/cds/saas-provisioning` |
+
+### Configuration {#saas-provisioning-config}
+
+```jsonc
+"cds.xt.SaasProvisioningService": {
+ "jobs": {
+ "queueSize": 5, // default: 100
+ "workerSize": 5, // default: 1
+ "clusterSize": 5, // default: 1
+ }
+}
+```
+
+- [Common Config Options](#common-config-options)
+- `jobs` β settings of the built-in job orchestrator
+ - `workerSize` β max number of parallel asynchronous jobs per database
+ - `clusterSize` β max number of database clusters, running `workerSize` jobs each
+ - `queueSize` β max number of jobs waiting to run in the job queue
+- `dependencies` β SAP BTP SaaS Provisioning service dependencies
+
+#### HTTP Request Options
+
+| Request Header | Example Value | Description |
+| ---------------- | -------------------------------------------------------|--------------|
+| `prefer` | `respond-async` | Trigger subscription, upgrade or unsubscription request asynchronously. |
+| `status_callback` | `/saas-manager/v1/subscription-callback/123456/result` | Callback path for SAP BTP SaaS Provisioning service. Set automatically if asynchronous subscription is configured for `saas-registry` service. |
+
+
+::: tip No `prefer: respond-async` needed with callback
+Requests are implicitly asynchronous when `status_callback` is set.
+:::
+
+##### Example Usage
+
+With `@sap/hdi-deploy` parameters `trace` and `auto_undeploy`:
+
+```http
+POST /-/cds/saas-provisioning/upgrade HTTP/1.1
+Content-Type: application/json
+
+{
+ "tenants": ["t1"],
+ "options": {
+ "_": {
+ "hdi": {
+ "deploy": {
+ "trace": "true",
+ "auto_undeploy": "true"
+ }
+ }
+ }
+ }
+}
+```
+
+### GET `tenant/` {#get-tenant}
+
+Returns tenant-specific metadata if `` is set, and a list of all tenants' metadata if omitted.
+
+| Parameters | Description |
+| ---------------- | ----------------------------------------------------------- |
+| `tenant` | A string identifying the tenant. |
+
+#### Example Usage {#example-tenant-metadata}
+
+##### Get Metadata for a Specific Tenant {#example-get-tenant-metadata}
+
+::: code-group
+
+```http [Request]
+GET /-/cds/saas-provisioning/tenant/t1 HTTP/1.1
+Content-Type: application/json
+```
+
+```json [Response]
+{
+ "subscribedTenantId": "tenant-1",
+ "eventType": "CREATE",
+ "subscribedSubdomain": "subdomain-1",
+ "subscriptionAppName": "app-1",
+ "subscribedSubaccountId": "subaccount-1",
+ "createdAt": "2023-11-10T14:36:22.639Z",
+ "modifiedAt": "2023-13-10T15:16:22.802Z"
+}
+```
+
+:::
+
+##### Get Metadata for All Tenants
+
+::: code-group
+
+```http [Request]
+GET /-/cds/saas-provisioning/tenant HTTP/1.1
+Content-Type: application/json
+```
+
+```json [Response]
+[
+ {
+ "subscribedTenantId": "tenant-1",
+ "eventType": "CREATE",
+ "subscribedSubdomain": "subdomain-1",
+ "subscriptionAppName": "app-1",
+ "subscribedSubaccountId": "subaccount-1",
+ "createdAt": "2023-11-10T14:36:22.639Z",
+ "modifiedAt": "2023-13-10T15:16:22.802Z"
+ },
+ {
+ "subscribedTenantId": "tenant-2",
+ "eventType": "CREATE",
+ "subscribedSubdomain": "subdomain-2",
+ "subscriptionAppName": "app-2",
+ "subscribedSubaccountId": "subaccount-2",
+ "createdAt": "2023-11-11T14:36:22.639Z",
+ "modifiedAt": "2023-11-12T12:14:45.452Z"
+ }
+]
+```
+
+:::
+
+### PUT `tenant/` (...) {#put-tenant}
+
+Creates tenant resources required for onboarding.
+
+Learn about query parameters, arguments, and their description in the following table:
+
+| Parameters | |
+| ---------------- | ------------------------------------------------------------------------ |
+| `tenant` | A string identifying the tenant |
+| Arguments |
+| `subscribedTenantId` | A string identifying the tenant |
+| `subscribedSubdomain` | A string identifying the tenant-specific subdomain |
+| `eventType` | The `saas-registry` event (`CREATE` or `UPDATE`) |
+
+#### Example Usage {#example-post-tenant}
+
+::: code-group
+
+```http [Request]
+PUT /-/cds/saas-provisioning/tenant/t1 HTTP/1.1
+Content-Type: application/json
+
+{
+ "subscribedTenantId": "t1",
+ "subscribedSubdomain": "subdomain1",
+ "eventType": "CREATE"
+}
+```
+
+```txt [Response]
+https://my.app.url
+```
+
+:::
+
+### DELETE `tenant/` {#delete-tenant}
+
+Deletes all tenant resources.
+
+### GET `dependencies` _β [{ xsappname }]_ {#get-dependencies}
+
+Returns configured SAP BTP SaaS Provisioning service dependencies.
+
+[Learn how to configure SaaS dependencies](./#saas-dependencies){.learn-more}
+
+### `upgrade` _[tenants] β Jobs_
+
+Use the `upgrade` endpoint to upgrade tenant base models.
+
+| Arguments | Description |
+| --------- | ----------------------------------------------------------- |
+| `tenants` | A list of tenants, or `[*]` for all tenants |
+
+#### Example Usage {#example-upgrade}
+
+##### Asynchronously Upgrade a List of Tenants
+
+::: code-group
+
+```http [Request]
+POST /-/cds/saas-provisioning/upgrade HTTP/1.1
+Content-Type: application/json
+Prefer: respond-async
+
+{
+ "tenants": ["t1", "t2"]
+}
+```
+
+```json [Response]
+{
+ "ID": "",
+ "createdAt": "",
+ "op": "upgrade",
+ "tenants": {
+ "t1": {
+ "ID": ""
+ }
+ }
+}
+```
+
+:::
+
+##### Asynchronously Upgrade All Tenants
+
+::: code-group
+
+```http [Request]
+POST /-/cds/saas-provisioning/upgrade HTTP/1.1
+Content-Type: application/json
+Prefer: respond-async
+
+{
+ "tenants": ["*"]
+}
+```
+
+```json [Response]
+{
+ "ID": "",
+ "createdAt": "",
+ "op": "upgrade",
+ "tenants": {
+ "t1": {
+ "ID": ""
+ }
+ }
+}
+```
+
+:::
+
+We recommended to execute the upgrades asynchronously by setting the `Prefer: respond-async` header.
+You can use the URL returned in the `Location` response header to poll the job status.
+
+In addition, you can poll the status for individual tenants using its individual task ID:
+
+```http
+GET /-/cds/jobs/pollTask(ID='') HTTP/1.1
+```
+
+The response is similar to the following:
+
+```js
+{
+ "status": "FINISHED",
+ "op": "upgrade"
+}
+```
+
+The job and task status can take on the values `QUEUED`, `RUNNING`, `FINISHED` and `FAILED`.
+
+
+
+## [Old MTX Reference](old-mtx-apis) {.toc-redirect}
+
+[See Reference docs for former 'old' MTX Services.](old-mtx-apis){.learn-more}
diff --git a/guides/multitenancy/old-mtx-apis.md b/guides/multitenancy/old-mtx-apis.md
new file mode 100644
index 000000000..c23e54b23
--- /dev/null
+++ b/guides/multitenancy/old-mtx-apis.md
@@ -0,0 +1,518 @@
+---
+shorty: Old MTX
+synopsis: >
+ API reference documentation for MTX Services.
+breadcrumbs:
+ - Cookbook
+ - Multitenancy
+ - Old MTX
+# layout: cookbook
+status: released
+---
+
+
+
+# Old MTX Reference
+
+{{ $frontmatter.synopsis }}
+
+All APIs receive and respond with JSON payloads. Application-specific logic (for example, scope checks) can be added using [Event Handlers](#event-handlers-for-cds-mtx-apis).
+
+::: tip _Streamlined MTX APIs_
+This is the reference documentation for our old MTX implementation. To find the API reference for our new, streamlined MTX, see [MTX Services Reference](mtxs). Also find instructions about [migrating to new MTX](old-mtx-migration).
+:::
+
+## Intro & Overview
+
+CAP provides `@sap/cds-mtx` as a Node.js module published on [npmjs.com](https://www.npmjs.com/package/@sap/cds-mtx).
+
+It provides a number of APIs for implementing SaaS applications on SAP BTP. All APIs are based on CDS. They can be exposed through plain REST, and/or consumed by other Node.js modules when running on the same server as `cds-mtx`:
+
++ _provisioning_: Implements the subscription callback API as required by SAP BTP. If a tenant subscribes to the SaaS application, the onboarding request is handled. `cds-mtx` contacts the SAP Service Manager to create a new HDI container for the tenant. Database artifacts are then deployed into this HDI container. In addition, the unsubscribe operation and the "get dependencies" operations are supported.
+
++ _metadata_: Can be used to get CSN and EDMX models, and to get a list of available services and languages.
+
++ _model_: Used to extend existing CDS models and to perform tenant model upgrades after having pushed a new version of the SaaS application.
+
+## Provisioning API
+
+
+
+### Subscribe Tenant
+
+```http
+PUT /mtx/v1/provisioning/tenant/ HTTP/1.1
+```
+
+Minimal request body:
+
+```json
+{
+ "subscribedSubdomain": "",
+ "eventType": "CREATE"
+}
+```
+
+Only if `eventType` is set to `CREATE`, the subscription is performed.
+
+
+An application can mix in application-specific parameters into this payload, which it can interpret within application handlers. Use the `_application_` object to specify those parameters. There's one predefined `sap` object, which is interpreted by `cds-mtx` default handlers. With that object, you can set service creation parameters to be used by the SAP Service Manager when creating HDI container service instances. A typical use case is to provide the `database_id` to distinguish between multiple SAP HANA databases mapped to one Cloud Foundry space.
+
+```json
+{
+ "subscribedSubdomain": "",
+ "eventType": "CREATE",
+ "_application_": {
+ "sap": {
+ "service-manager": {
+ "provisioning_parameters": { "database_id": "" }
+ }
+ }
+ }
+}
+```
+
+> If you have more than one SAP HANA database mapped to one space, subscription doesn't work out of the box, unless you've specified a default database.
+>
+
+You can also set a default database using the cds.mtx.provisioning.container environment configuration.
+
+As the `database_id` is only known when deploying the application, it's recommended to add the configuration as an environment variable in a _*.mtaext_ file for deployment only:
+
+```yaml
+ - name: bookshop-srv
+ type: nodejs
+ path: gen/srv
+ properties:
+ CDS_MTX_PROVISIONING_CONTAINER: { "provisioning_parameters": { "database_id": "" } }
+
+```
+
+The `provisioning_parameters` specified in the request overwrite the configured `provisioning_parameters`.
+
+### Unsubscribe Tenant
+
+```http
+DELETE /mtx/v1/provisioning/tenant/ HTTP/1.1
+```
+
+### Subscription Dependencies
+
+```http
+GET /mtx/v1/provisioning/dependencies HTTP/1.1
+```
+
+Response body: `Array of String`. The default implementation returns an empty array.
+
+### GET Subscribed Tenants
+
+```http
+GET /mtx/v1/provisioning/tenant/ HTTP/1.1
+```
+
+Returns the list of subscribed tenants. For each tenant, the request body that was used for subscribing the tenant is returned.
+
+## Model API
+
+### Get CDS Model Content
+
+```http
+GET mtx/v1/model/content/ HTTP/1.1
+```
+
+Returns the two objects `base` and `extension` in the response body:
+
+```json
+{
+ "base": "",
+ "extension": ""
+}
+```
+
+### Activate Extensions
+
+```http
+POST mtx/v1/model/activate HTTP/1.1
+```
+
+Request body (example):
+
+ ```json
+{
+ "tenant": "tenant-extended",
+ "extension": "",
+ "undeployExtension": false
+}
+ ```
+
+The `extension` element must be a JSON array of arrays. Each first-level array element corresponds to a CDS file containing CDS extensions. Each second-level array element must be a two-entry array. The first entry specifies the file name. The second entry specifies the file content. Extension files for data models must be placed in a `db` folder. Extensions for services must be placed in an `srv` folder. Entities of the base model (the non-extended model) are imported by `using ... from '_base/...'`.
+
+If the `undeployExtension` flag is set, all extensions are undeployed from the database that are no longer part of the extensions in the current activation call.
+
+::: warning _β Warning_
+`undeployExtension` has to be used with care as it potentially removes tables and their content from the database.
+:::
+
+Request body detailed sample:
+
+```json
+{
+"tenant": "tenant-extended",
+"extension": [
+ [
+ "db/ext-entities.cds",
+ "using my.bookshop from '_base/db/data-model'; \n extend entity bookshop.Books with { \n ISBN: String; \n rating: Integer \n }"
+ ],
+ [
+ "db/new-entities.cds",
+ "namespace com.acme.ext; \n entity Categories { \n key ID: String; \n description: String; \n }"
+ ],
+ [
+ "srv/ext-service.cds",
+ "using CatalogService from '_base/srv/cat-service'; \n using com.acme.ext from '../db/new-entities'; \n extend service CatalogService with { \n @insertonly entity Categories as projection on ext.Categories; \n }"
+ ]
+ ],
+"undeployExtension": false
+}
+```
+
+### Deactivate Extension
+
+```http
+POST /mtx/v1/model/deactivate HTTP/1.1
+```
+
+Request body (example):
+
+```json
+{
+ "tenant": "tenant-extended",
+ "extension_files": [
+ "srv/ext-service.cds"
+ ]
+}
+```
+
+`extension_files` is an array of the files that are to be removed from the extensions.
+
+Use this API to deactivate extension. To activate and deactivate an extension in one call, use `activate` with `undeployExtension: true`.
+
+::: warning _β Warning_
+The API has to be used with care as it removes tables and their content from the database.
+:::
+
+### Reset Extension
+
+```http
+POST /mtx/v1/model/reset HTTP/1.1
+```
+
+Request body (example):
+
+```json
+{
+ "tenant": "tenant-extended"
+}
+```
+
+Use this API to remove all extensions.
+
+::: warning _β Warning_
+The API has to be used with care as it removes tables and their content from the database.
+:::
+
+
+### Upgrade Base Model from Filesystem (Asynchronous)
+
+```http
+POST mtx/v1/model/asyncUpgrade HTTP/1.1
+```
+
+Request body:
+
+```json
+{
+ "tenants": ["tenant-extended-1", "tenant-non-extended-2", ...],
+ "autoUndeploy":
+}
+```
+
+Upgrade all tenants with request body `{ "tenants": ["all"] }`.
+
+If `autoUndeploy` is set to `true`, the auto-undeploy mode of the HDI deployer is used. See [HDI Delta Deployment and Undeploy Allow List](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c2b99f19e9264c4d9ae9221b22f6f589/ebb0a1d1d41e4ab0a06ea951717e7d3d.html) for more details.
+
+Response (example):
+
+```json
+{ "jobID": "iy5u935lgaq" }
+```
+
+You can use the ``jobID`` to query the status of the upgrade process:
+
+```http
+GET /mtx/v1/model/status/ HTTP/1.1
+```
+
+During processing, the response can look like this:
+
+```json
+{
+ "error": null,
+ "status": "RUNNING",
+ "result": null
+}
+```
+
+Once a job is finished, the collective status is reported like this:
+
+```json
+{
+ "error": null,
+ "status": "FINISHED",
+ "result": {
+ "tenants": {
+ "": {
+ "status": "SUCCESS",
+ "message": "",
+ "buildLogs": ""
+ },
+ "": {
+ "status": "FAILURE",
+ "message": "",
+ "buildLogs": ""
+ }
+ }
+ }
+}
+```
+
+The status of a job can be `QUEUED` (not started yet), `RUNNING`, `FINISHED`, or `FAILED`.
+
+The result status of the upgrade operation per tenant can be `RUNNING`, `SUCCESS`, or `FAILURE`.
+
+> Logs are persisted for a period of 30 minutes before they get deleted automatically. If you request the job status after that, you'll get a `404 Not Found` response.
+
+## Metadata API
+
+All metadata APIs support eTags. By [setting the corresponding header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag), you can check for model updates.
+
+### GET EDMX
+
+```http
+GET /mtx/v1/metadata/edmx/ HTTP/1.1
+```
+
+Returns the EDMX metadata of the (extended) model of the application.
+
+Optional URL parameters
+
+```http
+name=
+language=
+```
+
+### GET CSN
+
+```http
+GET /mtx/v1/metadata/csn/ HTTP/1.1
+```
+
+Returns the compiled (extended) model of the application.
+
+### GET Languages
+
+```http
+GET /mtx/v1/metadata/languages/ HTTP/1.1
+```
+
+Returns the supported languages of the (extended) model of the application.
+
+### GET Services
+
+```http
+GET /mtx/v1/metadata/services/ HTTP/1.1
+```
+
+Returns the services of the (extended) model of the application.
+
+## Diagnose API
+
+### GET Jobs
+
+```http
+GET /mtx/v1/diagnose/jobs HTTP/1.1
+```
+
+Returns information about the job queue, including waiting or running jobs.
+
+### GET Memory
+
+```http
+GET /mtx/v1/diagnose/memory HTTP/1.1
+```
+
+Returns information about the memory usage.
+
+### GET Container
+
+```http
+GET /mtx/v1/diagnose/container/ HTTP/1.1
+```
+
+Returns information about a tenant's HDI container.
+
+
+
+## Adding Custom Handlers { #event-handlers-for-cds-mtx-apis}
+
+> ---
+>
+> If you're using a CAP Java server, it re-exposes the APIs required by SAP BTP's SaaS Manager (the Provisioning API). We recommended leveraging the corresponding [Java-based mechanisms to add handlers](../../java/multitenancy#custom-logic) to these APIs. Handlers for the Model-API of `cds-mtx` must always be implemented on the Node.js server, because this API isn't re-exposed by the CAP Java runtime.
+>
+> ---
+
+`cds-mtx` APIs are implemented as CDS services. Therefore, service implementations can be overridden using [CDS event handlers](../../node.js/core-services#srv-on-before-after).
+For `cds-mtx` APIs, custom handlers have to be registered on the `mtx` event in a [custom `server.js`](../../node.js/cds-serve#custom-server-js):
+
+```js
+const cds = require('@sap/cds')
+
+cds.on('mtx', async () => {
+ const provisioning = await cds.connect.to('ProvisioningService')
+ provisioning.prepend(() => {
+ provisioning.on('UPDATE', 'tenant', async (req, next) => {
+ await next() // default implementation creating HDI container
+ return '/admin'
+ })
+ })
+})
+```
+
+See the following use cases for more examples.
+
+### Use Case: Implement a Tenant Provisioning Handler { #event-handlers-for-cds-mtx-provisioning}
+
+You can set an application entry point for the subscription in the SAP BTP Cockpit (usually a UI).
+
+Create a `provisioning.js` file in the `srv` folder:
+
+```js
+module.exports = (service) => {
+ service.on('UPDATE', 'tenant', async (req, next) => {
+ await next() // default implementation creating HDI container
+ return '/admin'
+ })
+}
+```
+
+In the provided code sample, you have to replace `` with the URL of your `bookshop-srv` application on Cloud Foundry. In this example, the _/admin_ endpoint is returned. It's important that this endpoint isn't protected (doesn't require a JWT token).
+
+Custom code after **asynchronous** provisioning can be invoked with handlers for the internal endpoint to create tenants. This endpoint is called for both synchronous and asynchronous provisioning:
+
+```js
+module.exports = (service) => {
+ service.on('createTenant', async (req, next) => {
+ await next() // default implementation creating HDI container
+ const { subscriptionData } = req.data // original request payload
+ // custom code
+ return '/admin'
+ })
+}
+```
+
+### Use Case: Handler for Tenant Upgrade { #event-handlers-for-cds-mtx-upgrade}
+
+To execute custom code for tenant upgrades (see also [Tenant upgrade API](old-mtx-apis)), you can add handlers for the upgrade API that is called by `cds-mtx`. This API is called for the synchronous, as well as the asynchronous upgrade for each tenant.
+
+```js
+module.exports = (service) => {
+ service.on('upgradeTenant', async (req, next) => {
+ await next() // call the upgrade
+ const {
+ instanceData, // HDI container metadata
+ deploymentOptions // additional deployment options, for example, `autoUndeploy`
+ } = cds.context.req.body
+ // custom code
+ })
+}
+```
+
+### Use Case: Handler for Database Deployment { #event-handlers-for-cds-mtx-hana-deployment}
+
+To add custom code to the deployment of your application model to the SAP HANA database, you can add handlers for the deployment API called by `cds-mtx`.
+
+This example dynamically adds an additional SAP HANA service to the environment, so it can be used through synonyms (see also [Enable Access to Objects in Another HDI Container](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c2b99f19e9264c4d9ae9221b22f6f589/4adba34bd86544a880db8f9f1e32efb7.html)):
+
+```js
+module.exports = (service) => {
+ service.before('deployToDb', async (context) => {
+ const {
+ sourceDir, // directory with generated SAP HANA sources
+ instanceData, // HDI container metadata
+ deploymentOptions // additional deployment options, for example, `autoUndeploy`
+ } = cds.context.req.body;
+ // ...
+ const hana = [{
+ "label": "hana",
+ "provider": null,
+ "plan": "hdi-shared",
+ "name": "common-db-sample",
+ "": "value"
+ }];
+ context.data.additionalServices.hana = hana;
+ });
+}
+```
+
+## Appendix β Configuration
+
+### App Router { #approuter-config}
+
+Configure your App Router as follows.
+
+1. Enable token forwarding, for example:
+
+ ::: code-group
+
+ ```yaml [mta.yaml]
+ - name: approuter
+ requires:
+ - name: mtx-sidecar
+ group: destinations
+ properties:
+ name: mtx-sidecar
+ url: ~{url}
+ forwardAuthToken: true
+ ```
+
+ :::
+
+2. Configure a [route to MTX-Sidecar](../extensibility/customization#app-router) with authentication data being passed on to MTX for verification.
+
+ You may have to adjust the `destination` name according to your configuration for MTX in [mta.yaml](https://help.sap.com/docs/CP_CONNECTIVITY/cca91383641e40ffbe03bdc78f00f681/8aeea65eb9d64267b554f64a3db8a349.html)
+ or [manifest.yml](https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/3cc788ebc00e40a091505c6b3fa485e7.html#destinations), for example:
+
+ ```yaml
+ modules:
+ - name: sidecar
+ provides:
+ - name: mtx-sidecar
+ properties:
+ url: ${default-url}
+ ```
+
+
+
+
+
+## [Old SaaS Extensibility Guide](../extensibility/assets/customization-old) {.toc-redirect}
+
+[See the old guide for Extending and Customizing SaaS Solutions.](../extensibility/assets/customization-old)
+
+## Further Readings
+
+SAP BTP concepts for multitenancy are described in detail:
+
++ [Developing Multitenant Applications in the Cloud Foundry Environment](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/5e8a2b74e4f2442b8257c850ed912f48.html)
+
+
diff --git a/guides/multitenancy/old-mtx-migration.md b/guides/multitenancy/old-mtx-migration.md
new file mode 100644
index 000000000..59d804292
--- /dev/null
+++ b/guides/multitenancy/old-mtx-migration.md
@@ -0,0 +1,632 @@
+---
+shorty: MTX Migration
+synopsis: >
+ Explains how to migrate from @sap/cds-mtx (aka Old MTX) to 'streamlined' @sap/cds-mtxs.
+breadcrumbs:
+ - Cookbook
+ - Multitenancy
+ - Migration
+# layout: cookbook
+status: released
+impl-variants: true
+---
+
+# Migration from Old MTX {#migration}
+
+Towards new multitenancy capabilities
+{.subtitle}
+
+
+
+
+
+::: warning
+Make sure that you always use the latest version of the CAP modules using `npm outdated`. For Java, also check the versions configured in `pom.xml` files.
+:::
+
+## Functional Differences
+
+Before you start to migrate to `@sap/cds-mtxs`, read about the differences compared to the old MTX.
+
+### Persistence Changes
+
+With `@sap/cds-mtxs`, the persistence has been simplified. There's no second container needed (META-tenant) any longer. Instead, tenant-specific metadata, such as extensions, are stored in the same container as the application data.
+
+
+
+In addition, `@sap/cds-mtxs` also uses a dedicated tenant `t0` to store some runtime data, such as job logs.
+
+### Extensibility
+
+#### Changes of Extension Persistence
+
+In contrast to `@sap/cds-mtx`, with `@sap/cds-mtxs`, the extensions are no longer stored as sources, but only as compiled `csn` files. Instead of running a build on the server with each extension activation, the build is now run locally _before_ the extension is deployed. The extensions are then stored as `csn` files with a `tag` as key. When using [`cds push`](../extensibility/customization#push-extension), the `tag` is derived from the name of the extension project in `package.json`.
+
+Example `package.json` of extension project:
+
+```json
+{
+ "name": "@capire/orders-ext",
+ "extends": "@capire/orders",
+ ...
+}
+
+```
+
+When the extension is pushed, it is stored with the tag `@capire/orders-ext`.
+
+Also check the [Push API](mtxs#extensibilityservice).
+
+#### Handling of extension sources
+
+As mentioned previously, `cds push` only uploads compiled extensions as CSN files. Thus, it's no longer possible to download the CDS sources from the server. Source control is expected to be done by the SaaS application provider using his own repository.
+
+### Security
+
+Some of the roles have changed with `@sap/cds-mtxs`.
+
+| @sap/cds-mtx | @sap/cds-mtxs |
+| ----------------- | ------------------------ |
+| `ExtendCDS` | `cds.ExtensionDeveloper` |
+| `ExtendCDSdelete` | w/o replacement |
+
+## Permanent and Temporary Limitations
+
+### Temporary Limitations
+
+- Diagnose API isn't available.
+- Upload of extension only works synchronously.
+
+### Permanent Limitations
+
+- Scopes aren't configurable.
+
+
+- It isn't possible to have tenant-specific model versions.
+- Use of SAP HANA hdbmigrationtable is only possible for entities that aren't to be extended.
+- Upload of arbitrary custom files together with extensions is no longer available.
+
+## Migration Steps
+
+To switch to `@sap/cds-mtxs`, you need to change your project configuration, your custom handlers, and you might need to update the database content.
+
+{style="width:500px"}
+
+### Adapt Project Configuration
+
+
+
+#### Switch to `@sap/cds-mtxs`
+
+To switch your Node.js project to `@sap/cds-mtxs`, perform the following steps:
+
+1. Remove `@sap/cds-mtx`:
+ ```sh
+ npm remove @sap/cds-mtx
+ ```
+2. Add `@sap/cds-mtxs`:
+ ```sh
+ npm add @sap/cds-mtxs
+ ```
+3. Open your _package.json_ and add the following:
+
+ ```json
+ "cds": {
+ "requires": {
+ "multitenancy": true
+ }
+ }
+
+ ```
+
+#### Enable Extensibility
+
+If your project supports extensibility, you need to enable extensibility in your configuration.
+To do so, you only need to add `extensibility: true` to your cds configuration in `.cdsrc.json` or `package.json`.
+
+```json
+"requires": {
+ "multitenancy": true,
+ "extensibility": true
+}
+```
+
+
+
+
+
+#### Create New Sidecar and Adapt mta.yaml
+
+To create a sidecar based on `@sap/cds-mtxs`, you can use the following command:
+
+```sh
+cds add multitenancy
+```
+
+It creates a new sidecar folder _mtx/sidecar_ and also modifies other files, including _mta.yaml_.
+Currently, as `cds add multitenancy` is meant to be used with new projects, the best way is to **revert** the changes that have been made to _mta.yaml_ and to make a few manual changes instead.
+
+##### Remove Global Build Section
+
+The global build section can be removed. The necessary build script has moved to the sidecar module.
+
+```yaml
+# build-parameters:
+# before-all:
+# - builder: custom
+# commands:
+# - npm install --production
+# - npx -p @sap/cds-dk cds build --production
+```
+
+##### Add MTXS Flag to Java Module
+
+To switch the runtime module to `@sap/cds-mtxs`, you need to add the corresponding environment variable:
+
+```yaml
+requires:
+ ...
+ - name: mtx-sidecar
+ properties:
+ CDS_MULTITENANCY_MTXS_ENABLED: true # Only required for cds-services version 2
+ CDS_MULTITENANCY_SIDECAR_URL: ~{url}
+```
+
+#### Adapt _mta.yaml_ to Use New Sidecar
+
+To enable the newly created sidecar, you need to change the path of your existing sidecar to the new path.
+You only need to adapt the path to `mtx/sidecar` and add a custom build section.
+
+::: code-group
+
+```yaml [mta.yaml]
+modules:
+ - name: bookshop-mtx
+ type: nodejs
+ path: mtx/sidecar # adapted path
+ build-parameters: # added build section
+ builder: custom
+ build-result: gen
+ commands:
+ - npm run build
+ requires:
+ - name: bookshop-srv
+ parameters:
+ memory: 256M
+ disk-quota: 1G
+ requires:
+ - name: bookshop-auth
+ - name: bookshop-db
+ provides:
+ - name: mtx-api
+ properties:
+ mtx-url: ${default-url}
+```
+
+:::
+
+#### Add Workspace for Sidecar in Root package.json
+
+To make the `@sap/cds-mtxs` models part of the installation, add a workspace to the root `package.json` to include the sidecar dependencies.
+
+```json
+"workspaces": [
+ "mtx/sidecar"
+]
+```
+
+::: tip Freeze Sidecar Dependencies
+To prepare the build of the MTA archive (`mbt build`), you need to generate a `package-lock.json` for the sidecar by executing this in the project root:
+
+```sh
+npm i --package-lock-only --prefix mtx/sidecar
+```
+
+:::
+
+#### Adapt Build Tasks
+
+`cds add multitenancy` also adapts the build tasks in `.cdsrc.json` or `package.json`.
+You only need to remove the `mtx` build task.
+
+If your project uses the default project layout, all build tasks can be removed from the build configuration as follows:
+
+```json
+{
+ "build": {
+ "target": "."
+ },
+ "profiles": ["with-mtx-sidecar", "java"],
+ "requires": {
+ "multitenancy": true
+ }
+}
+```
+
+#### Enable Extensibility
+
+If your project supports extensibility, you need to enable extensibility in your configuration.
+To do so, you only need to add `extensibility: true` to your cds configuration in `.cdsrc.json` or `package.json`.
+
+```json
+"requires": {
+ "multitenancy": true,
+ "extensibility": true
+ }
+```
+
+
+
+#### Security Adaptations
+
+The scopes needed by extension developers have changed.
+Scopes `ExtendCDS` and `ExtendCDSdelete` have changed to `cds.ExtensionDeveloper`.
+Make sure to adapt all occurrences in your security configuration (`xs-security.json`).
+
+Communicate to customer admins and extension developers to add the new scope to their role collection.
+Also adjust the documentation for the SaaS application accordingly if available.
+
+
+
+#### Handler Registration
+
+A typical handler registration in `server.js` now looks like
+
+```js
+cds.on('served', async () => {
+ const { 'cds.xt.SaasProvisioningService': provisioning } = cds.services
+ const { 'cds.xt.DeploymentService': deployment } = cds.services
+
+ await provisioning.prepend(() => {
+ provisioning.on('UPDATE', 'tenant', async (req, next) => { ... })
+ provisioning.on('dependencies', async (req, next) => { ... })
+ ...
+ })
+ await deployment.prepend(() => {
+ // previously this was `upgradeTenant`
+ deployment.on('upgrade', async (req) => {
+ // HDI container credentials are not yet available here
+ })
+ // previously this was `deployToDb`
+ deployment.on('deploy', async (req) => {
+ const { tenant, options: { container } } = req.data
+ ...
+ })
+ ...
+ })
+})
+```
+
+Here's what has changed:
+
+- `ProvisioningService` changed to `cds.xt.SaasProvisioningService`
+- `DeploymentService` changed to `cds.xt.DeploymentService`
+- Use `cds.on('served')` instead of `cds.on('mtx')`.
+
+For Node.js, the `saas-registry` endpoints in `mta.yaml` need to be changed to `.../-/cds/saas-provisioning/...`:
+
+```yaml
+parameters:
+ service: saas-registry
+ config:
+ appUrls:
+ getDependencies: ~{mtx-api/mtx-url}/-/cds/saas-provisioning/dependencies
+ onSubscription: ~{mtx-api/mtx-url}/-/cds/saas-provisioning/tenant/{tenantId}
+```
+
+
+
+#### Miscellaneous Configuration
+
+`@sap/cds-mtx` offers some additional configuration that you can also set in `@sap/cds-mtxs`.
+
+##### HDI Container Configuration
+
+In `@sap/cds-mtx`, you can configure the HDI container creation as follows:
+
+```json
+"mtx": {
+ "provisioning": {
+ "lazymetadatacontainercreation": true,
+ "container": {
+ "provisioning_parameters": {
+ "database_id": ""
+ },
+ "binding_parameters": {
+ "key": "value"
+ }
+ },
+ "metadatacontainer": {
+ "provisioning_parameters": {
+ "database_id": ""
+ }
+ }
+ }
+}
+```
+
+In `@sap/cds-mtxs`, you can do the same configuration for the `cds.xt.DeploymentService`:
+
+```json
+"requires": {
+ "cds.xt.DeploymentService": {
+ "lazyT0": true,
+ "hdi": {
+ "create": {
+ "database_id": ""
+ },
+ "bind": {
+ "key": "value"
+ }
+ },
+ "for": {
+ "t0": {
+ "hdi": {
+ "create": {
+ "database_id": ""
+ }
+ }
+ }
+ }
+ },
+}
+```
+
+##### Extension Restrictions
+
+This configuration allows to set what extensions are allowed.
+
+With `@sap/cds-mtx`:
+
+```json
+"mtx" : {
+ "extension-allowlist": [
+ {
+ "for": ["my.bookshop.Authors", "my.bookshop.Books"],
+ "new-fields": 2
+ },
+ {
+ "for": ["CatalogService"]
+ }
+ ]
+}
+```
+
+With `@sap/cds-mtxs`, the same configuration has moved to the `cds.xt.ExtensibilityService` configuration:
+
+```json
+"requires": {
+ "cds.xt.ExtensibilityService": {
+ "extension-allowlist": [
+ {
+ "for": ["my.bookshop.Authors", "my.bookshop.Books"],
+ "new-fields": 2
+ },
+ {
+ "for": ["CatalogService"]
+ }]
+ }
+}
+```
+
+### Migrate Tenant Content of Existing Applications
+
+Depending on the MTX features that your existing application has used, you need to execute some steps to move your data to the persistence used by `@sap/cds-mtxs`.
+
+#### Multitenancy Only
+
+In case you only used the multitenancy features such as subscription/unsubscription, you just need to make the [configuration changes described earlier](#adapt-project-configuration).
+
+::: tip When does this scenario apply?
+
+- Your application doesn't support extensibility.
+- You don't need to read all tenant IDs or the tenant metadata using
+ `GET /-/cds/saas-provisioning/tenant/` or
+ `GET /-/cds/saas-provisioning/tenant/`.
+
+The tenant metadata is the data that is sent to the MTX API by the SAP BTP SaaS Provisioning Service on subscription, similar to this:
+
+```json
+{
+ "subscriptionAppId": "...",
+ "subscriptionAppName": "..." ,
+ "subscribedTenantId": "...",
+ ...
+}
+```
+
+:::
+
+See [project configuration](#adapt-project-configuration).
+
+#### Saving Subscription Metadata
+
+If your application needs access to the tenant list or tenant metadata, you need to update this data for `@sap/cds-mtxs`.
+
+::: tip When does this scenario apply?
+
+- Your application doesn't support extensibility.
+- Your application needs to read all tenant IDs or the tenant metadata using `GET /-/cds/saas-provisioning/tenant/` or
+ `GET /-/cds/saas-provisioning/tenant/`.
+ :::
+
+In order to copy the metadata from existing subscriptions to the new persistence of `@sap/cds-mtxs`, you need to run [a migration script](#run-the-migration-script) that comes with `@sap/cds-mtxs`.
+
+#### Migration of Extensions
+
+If your application supports extensibility, you also need to update the existing extensions for `@sap/cds-mtxs`. You can do this with the [same migration script](#run-the-migration-script).
+
+::: tip When does this scenario apply?
+
+- Your application supports extensibility.
+ :::
+
+#### Run the Migration Script
+
+The migration script is part of `@sap/cds-mtxs`. You can run it locally or during application deployment. Before running the script, you need to make the [configuration changes](#adapt-project-configuration) mentioned earlier.
+
+##### Run the Migration Script Locally
+
+The script has to run in the (Node.js) application environment resulting from `cds build --production` to correctly simulate the execution in the deployment environment.
+For Node.js applications, this result is the `gen/srv` folder generated in the application root, for Java applications, this result is the `gen` folder of the new `@sap/cds-mtxs` sidecar (`mtx/sidecar/gen`).
+
+It also needs access to the application bindings. That means, when running locally, it has to [run in hybrid mode](../../advanced/hybrid-testing#run-with-service-bindings).
+
+You also need to add the `production` profile to ensure that the models are resolved correctly.
+
+::: tip
+Make sure, that the sources you want to migrate have the exact same version on your local machine as the sources that are deployed to the `@sap/cds-mtx` application .
+:::
+
+Example:
+
+```sh
+cds migrate "*" --dry --profile hybrid,production --resolve-bindings
+```
+
+##### Options
+
+To run the migration for all or a set of tenants, you need to run:
+
+```sh
+cds migrate [,]|"*"
+```
+
+The option `--dry` allows you to perform a dry run, without changing the database content.
+
+Keep in mind that, depending on the number of tenants, the script requires some time to run. This is important when you consider to run it in combination with the application deployment.
+
+If the migration was successful, tenants are marked as migrated. When running the migration a second time, these tenants are ignored. If you want to rerun the migration also for the already migrated tenants, you can do so by using parameter `--force`.
+
+##### Save Existing Extension Projects
+
+You can use the migration script to save the content of the subscribers' extension projects.
+
+With parameter `-d`, you can specify a directory that is used by the script to store the existing, migrated extension projects.
+
+```sh
+cds migrate [,]|"*" -d
+```
+
+To really access the saved extension projects, you need access to the file system, of course. So, the easiest way is to run the script locally for that.
+
+##### Add the Migration Script as Cloud Foundry Task to mta.yaml
+
+You can add the migration script as a `hook` to your Node.js server module (application or sidecar) in _mta.yaml_.
+For that, you can use the script `cds-mtx-migrate` that also comes with the `@sap/cds-mtxs` but doesn't require `@sap/cds-dk` to be installed.
+Example:
+
+```yaml
+- name: bookshop-mt-sidecar
+ type: nodejs
+ path: mtx/sidecar
+ ...
+ hooks:
+ - name: migrate-tenants
+ type: task
+ phases:
+ # - blue-green.application.before-start.idle
+ - deploy.application.before-start
+ parameters:
+ name: migration
+ memory: 512M
+ disk-quota: 768M
+ command: cds-mtx-migrate "*"
+```
+
+See also [Module Hooks](https://help.sap.com/docs/btp/sap-business-technology-platform/module-hooks)
+::: warning
+Warning: In case you already run an upgrade as task and your project supports extensions, make sure that the upgrade is run **AFTER** the migration. Otherwise, the content of extended tables can get lost.
+:::
+
+##### Advanced: Separate Extensions Based on Extension File Names
+
+The concept of extensions has slightly changed with `@sap/cds-mtxs`. Extensions sources are no longer stored in the backend. Instead, each extension gets a _tag_ and the extension is stored as `csn` with the _tag_ as key.
+When running the migration script, all extension files are compiled to one `csn` and are stored with a default _tag_: `migrated`.
+
+You can change the _default tag_ by passing your _own tag_ using the `--tag` parameter:
+
+```sh
+cds migrate "*" -d migrated_projects --tag "mytag"
+```
+
+In addition, you can separate your extensions into several `csn`-files with different tags. For example, if your original extension files follow a pattern, you can do so by passing parameter `--tagRule` with a regular expression.
+
+Let's use the following extension project structure:
+
+```zsh
+old-bookshop-ext/
+βββ db/
+β βββ extension_id_1.cds
+β βββ extension_id_2.cds
+β βββ order_ext_id_1.cds
+β βββ order_ext_id_2.cds
+βββ srv/
+βββ package.json
+```
+
+You can split your extensions as follows:
+
+```sh
+cds migrate "*" -d migrated_projects --tagRule "(?:ext_|extension_)(.*)\.cds"
+```
+
+As a result, you get two extensions with tags `id_1` and `id_2`. The _tag_ is taken from the first captured group of the regular expression.
+
+::: tip Find the right regular expression
+To verify if the result meets your expectations, you can make a dry run:
+
+```sh
+cds migrate "*" -d migrated_projects --tagRule "(?:ext_|extension_)(.*)\.cds" --dry
+```
+
+You can find the result in the folder _migrated_projects_.
+:::
+
+
+
+### Check Migration Result
+
+To verify the result of the migration script, check the tenant's content of the HDI container. You can use any database client that can access SAP HANA databases.
+
+#### Check Content Using SAP HANA Database Explorer
+
+To see the content of an HDI Container, you can [add the tenant container to the SAP HANA Database Explorer](https://help.sap.com/docs/HANA_CLOUD/a2cea64fa3ac4f90a52405d07600047b/4e2e8382f8484edba31b8b633005e937.html).
+
+You can find the migrated extensions in table `CDS_XT_EXTENSIONS`. The table contains:
+
+- extensions parsed as `csn` strings in column **csn**
+- key column **tag**
+
+{ .adapt style="width:800px; box-shadow: 1px 1px 5px #888888"}
+
+## Migrated Extension Projects
+
+As mentioned in [Save Existing Extension Projects](#save-existing-extension-projects), you can store existing extension projects locally.
+We recommend to upload the projects to a source repository (e. g. github), because with `@sap/cds-mtxs` the content of extension projects is no longer stored in the tenant database. With that setup you can change and push the extension again later.
+
+The content of extension projects is usually the property of the customer (subscriber). So, alternatively, the customer can [download](#download-of-migrated-extension-projects) the extension projects himself and upload them to his own source repository.
+
+### Adapt for Streamlined MTX
+
+As described in the [extensibility guide](../extensibility/customization##start-ext-project), you usually start with an empty extension project and pull the base model of the application using [`cds pull`](../extensibility/customization#pull-base).
+
+When starting with a migrated extension project, you need to make some adaptations after running [`cds pull`](../extensibility/customization#pull-base). Previously, extension projects were using the full set of CDS files whereas extension projects based on `@sap/cds-mtxs` are using a compiled `index.csn` of the base model. This affects the references in the extension sources of the migrated project. So these references need to be adapted.
+
+Recommended steps:
+
+- Run [`cds pull`](../extensibility/customization#pull-base) to fetch the latest version of the base model as `index.csn`.
+- Fix the references in your extension sources. All references to the base model must use the name specified in the `cds.extends` entry of the extension _package.json_, omitting any additional subfolders.
+ Example: `using sap.capire.bookshop from '_base/db/schema';` must be replaced by `using sap.capire.bookshop from 'base-model';`
+ You can see all broken references as error messages when using the CDS Editor.
+
+### Download of Migrated Extension Projects
+
+As long as the metadata containers (`TENANT--META`) created by `@sap/cds-mtx` still exist, the customer extension projects can be downloaded using the CDS client. The [user](../extensibility/customization#cds-login) running the download command needs to have the scope `cds.ExtensionDeveloper` assigned:
+
+```sh
+cds extend --download-migrated-projects
+```
+The command downloads an archive named `migrated_projects.tgz` that contains the existing extensions that are ready to be used with `@sap/cds-mtxs`.
+
+
diff --git a/guides/providing-services.md b/guides/providing-services.md
index 69d08b0a3..ba9a7fe60 100644
--- a/guides/providing-services.md
+++ b/guides/providing-services.md
@@ -379,7 +379,7 @@ Searches the `title` element only.
##### Extend Search to *Associated* Entities
::: warning Node.js: Only w/ streamlined database services
-For Node.js projects, this feature is only available with the [streamlined `@cap-js/` database services](../releases/jun24#new-database-services-ga) (default with `@sap/cds` >= 8)
+For Node.js projects, this feature is only available with the [streamlined `@cap-js/` database services](../releases/archive/2024/jun24#new-database-services-ga) (default with `@sap/cds` >= 8)
:::
```cds
@@ -763,6 +763,16 @@ In addition to server-side input validation as introduced above, this adds a cor
+### `@Common.FieldControl`
+{#common-fieldcontrol}
+
+The input validation for `@Common.FieldControl: #Mandatory` and `@Common.FieldControl: #ReadOnly` is done from the CAP runtimes automatically.
+::: warning
+Custom validations are required when using static or dynamic numeric values, for example, `@Common.FieldControl: 1` or `@Common.FieldControl: integer_field`.
+:::
+
+
+
### `@assert .unique`
Annotate an entity with `@assert.unique.`, specifying one or more element combinations to enforce uniqueness checks on all CREATE and UPDATE operations. For example:
@@ -1110,7 +1120,11 @@ GET .../sue/Foo(2)/Sue.getStock() // bound function
POST .../sue/Foo(2)/Sue.order {"x":1} // bound action
```
-> Note: You always need to add the `()` for functions, even if no arguments are required. The OData standard specifies that bound actions/functions need to be prefixed with the service's name. In the previous example, entity `Foo` has a bound action `order`. That action must be called via `/Foo(2)/Sue.order` instead of simply `/Foo(2)/order`. For convenience, however, the Node.js runtime also allows calling bound actions/functions without prefixing them with the service name.
+> Note: You always need to add the `()` for functions, even if no arguments are required. The OData standard specifies that bound actions/functions need to be prefixed with the service's name. In the previous example, entity `Foo` has a bound action `order`. That action must be called via `/Foo(2)/Sue.order` instead of simply `/Foo(2)/order`.
+> For convenience, the CAP Node.js runtime also allows the following:
+> - Call bound actions/functions without prefixing them with the service name.
+> - Omit the `()` if no parameter is required.
+> - Use query options to provide function parameters like `sue/sum?x=1&y=2`
diff --git a/guides/using-services.md b/guides/using-services.md
index 69d775245..733abba9e 100644
--- a/guides/using-services.md
+++ b/guides/using-services.md
@@ -786,8 +786,6 @@ The list of [required implementations for mashups](#required-implementations-for
Expands add data from associated entities to the response. For example, for a risk, you want to display the suppliers name instead of just the technical ID. But this property is part of the (remote) supplier and not part of the (local) risk.
-[Get more details in the end-to-end tutorial.](https://developers.sap.com/tutorials/btp-app-ext-service-consume-ui.html#7d36d433-2b88-407c-a6cc-d6a05dcc8547){.learn-more}
-
To handle expands, you need to add a handler for the main entity:
1. Check if a relevant `$expand` column is present.
2. Remove the `$expand` column from the request.
@@ -1610,18 +1608,18 @@ The Node.js runtime supports `odata` as an alias for `odata-v4` as well.
### Querying API Features
-| Feature | Java | Node.js |
-|-----------------------------------|:----:|:-------:|
-| READ | | |
-| INSERT/UPDATE/DELETE | | |
-| Actions | | |
-| `columns` | | |
-| `where` | | |
-| `orderby` | | |
-| `limit` (top & skip) | | |
-| `$apply` (groupedby, ...) | | |
-| `$search` (OData v4) | | |
-| `search` (SAP OData v2 extension) | | |
+| Feature | Java | Node.js |
+|------------------------------------|:----:|:-------:|
+| READ | | |
+| INSERT/UPDATE/DELETE | | |
+| Actions | | |
+| `columns` | | |
+| `where` | | |
+| `orderby` | | |
+| `limit` (top & skip) | | |
+| `$apply` (aggregate, groupby, ...) | | |
+| `$search` (OData v4) | | |
+| `search` (SAP OData v2 extension) | | |
### Supported Projection Features
diff --git a/java/assets/architecture-mt.drawio.svg b/java/assets/architecture-mt.drawio.svg
new file mode 100644
index 000000000..0e2ca7b7c
--- /dev/null
+++ b/java/assets/architecture-mt.drawio.svg
@@ -0,0 +1,289 @@
+
\ No newline at end of file
diff --git a/java/assets/kibana.png b/java/assets/kibana.png
new file mode 100644
index 000000000..a6ac0ad02
Binary files /dev/null and b/java/assets/kibana.png differ
diff --git a/java/change-tracking.md b/java/change-tracking.md
index e31edcf89..61d4225ea 100644
--- a/java/change-tracking.md
+++ b/java/change-tracking.md
@@ -19,7 +19,7 @@ to the remote services aren't tracked.
## Enabling Change Tracking
-To use the change tracking feature, you need to add a dependency to [cds-feature-change-tracking](https://central.sonatype.com/artifact/com.sap.cds/cds-feature-change-tracking) in the `pom.xml` file of your service:
+To use the change tracking feature, you need to add a dependency to [cds-feature-change-tracking](https://central.sonatype.com/artifact/com.sap.cds/cds-feature-change-tracking) in the `srv/pom.xml` file of your service:
```xml
diff --git a/java/developing-applications/building.md b/java/developing-applications/building.md
index c3131f6c5..257f40400 100644
--- a/java/developing-applications/building.md
+++ b/java/developing-applications/building.md
@@ -377,30 +377,89 @@ Use the _.cdsrc.json_ file to add project specific configuration of `@sap/cds-dk
### Using a Local cds-dk
-By default, the build is configured to download a Node.js runtime and the `@sap/cds-dk` tools and install them locally within the project.
-The `install-cdsdk` goal requires a version of `@sap/cds-dk`, which [needs to be provided explicitly](../../releases/archive/2022/oct22#important-changes-in-java) in the configuration. With this, you can ensure that the build is fully reproducible.
-You can provide this version by adding the following property to the `properties` section in your `pom.xml`:
-
-```xml
-
- ...
- FIXED VERSION
-
+Starting with version 3.6.0 of the `cds-services-archetype`, the default setup of a newly created CAP Java project has changed. The `@sap/cds-dk` is maintained as a `devDependency` in `package.json` and installed with an `npm ci` during the Maven build.
+The `install-cdsdk` goal is no longer used to install the `@sap/cds-dk` locally and it's also marked as deprecated. The version of the `@sap/cds-dk` is no longer maintained in _pom.xml_, it's configured in the _package.json_:
+```json
+{
+ "devDependencies" : {
+ "@sap/cds-dk" : "^8.5.1",
+ }
+}
```
+A `package-lock.json` is also created during project creation with the `cds-services-archetype`. The lock file is needed for `npm ci` to run successfully and pinsΒ the transitive dependencies of `@sap/cds-dk` to fixed versions. Fixing the versions ensures that the CDS build is fully reproducible.
::: warning
-Make sure to regularly update `@sap/cds-dk` according to [our guidance](../../releases/schedule).
-
For multitenant applications, ensure that the `@sap/cds-dk` version in the sidecar is in sync.
:::
+#### Migrate From Goal `install-cdsdk` to `npm ci`
+{ #migration-install-cdsdk }
+
+To migrate from the deprecated goal `install-cdsdk` to the new `npm ci` approach, the following steps are required:
+
+1. Remove execution of goal `install-cdsdk` from the `cds-maven-plugin` in _srv/pom.xml_:
+ ```xml
+
+ com.sap.cds
+ cds-maven-plugin
+ ${cds.services.version}
+
+
+
+ cds.install-cdsdk
+
+ install-cdsdk
+
+
+
+ ```
+
+2. Then add execution of goal `npm` with arguments `ci` instead to the `cds-maven-plugin` in _srv/pom.xml_:
+ ```xml
+
+ cds.npm-ci
+
+ npm
+
+
+ ci
+
+
+ ```
+
+3. Remove cds-dk version property `cds.install-cdsdk.version` from _pom.xml_:
+ ```xml
+
+
+ 8.4.2
+
+
+ ```
+
+4. Add `@sap/cds-dk` as devDependency to _package.json_:
+ ```json
+ {
+ "devDependencies" : {
+ "@sap/cds-dk" : "^8.5.0"
+ }
+ }
+ ```
+
+5. Perform `npm install` on the command line to get the _package-lock.json_ created or updated.
+
+6. Finally, do a `mvn clean install` and verify that the installation of `@sap/cds-dk` is done with the new approach.
+
#### Maintaining cds-dk
-By default, the goal `install-cdsdk` of the `cds-maven-plugin` skips the installation of the `@sap/cds-dk`, if the `@sap/cds-dk` is already installed.
-To update the `@sap/cds-dk` version in your application project do the following:
+1. _package.json_ and `npm ci`
+Newly created CAP Java projects maintain the `@sap/cds-dk` with a specific version as a devDependency in `package.json`. So, when you update the version, run npm install from the command line to update the `package-lock.json`. `npm ci` will then install the updated version of `@sap/cds-dk`.
+
+2. Goal `install-cdsdk`
+ Older CAP Java projects that use the `install-cdsdk` goal of the `cds-maven-plugin` don't update `@sap/cds-dk`. By default, the goal skips the installation if it's already installed.
+To update the `@sap/cds-dk` version:
-1. Specify a newer version of `@sap/cds-dk` in your *pom.xml* file.
-2. Execute `mvn spring-boot:run` with an additional property `-Dcds.install-cdsdk.force=true`, to force the installation of a **`@sap/cds-dk`** in the configured version.
+3. Specify a newer version of `@sap/cds-dk` in your *pom.xml* file.
+4. Execute `mvn spring-boot:run` with an additional property `-Dcds.install-cdsdk.force=true`, to force the installation of a **`@sap/cds-dk`** in the configured version.
```sh
mvn spring-boot:run -Dcds.install-cdsdk.force=true
diff --git a/java/event-handlers/indicating-errors.md b/java/event-handlers/indicating-errors.md
index 63021059e..057c445b7 100644
--- a/java/event-handlers/indicating-errors.md
+++ b/java/event-handlers/indicating-errors.md
@@ -313,10 +313,6 @@ public void validateReview(BooksAddReviewContext context) {
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Invalid book description")
.messageTarget(b -> b.get("descr"));
- // which is equivalent to
- throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Invalid book description")
- .messageTarget(b -> b.descr());
-
// or (using the typed API, referring to "cqn" implicitly)
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Invalid book description")
.messageTarget(Books_.class, b -> b.descr());
diff --git a/java/fiori-drafts.md b/java/fiori-drafts.md
new file mode 100644
index 000000000..8652fa3e9
--- /dev/null
+++ b/java/fiori-drafts.md
@@ -0,0 +1,256 @@
+---
+synopsis: >
+ This section describes which events occur in combination with SAP Fiori Drafts.
+status: released
+uacp: Used as link target from SAP Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/9186ed9ab00842e1a31309ff1be38792.html
+---
+
+# Fiori Drafts
+
+
+
+{{ $frontmatter.synopsis }}
+
+## Overview { #draftevents}
+
+See [Cookbook > Serving UIs > Draft Support](../advanced/fiori#draft-support) for an overview on SAP Fiori Draft support in CAP.
+
+## Reading Drafts
+
+When enabling an entity for draft, an additional set of database tables is created for the entity composition tree. These database tables are used to store the drafts.
+When reading draft-enabled entities, data from the active entity and the drafts is merged into a joint result. As part of this, draft-specific elements like `IsActiveEntity`, `HasActiveEntity` or `HasDraftEntity` are calculated.
+
+The standard `READ` event of a `CqnService` orchestrates the delegation of the query to the active entity and the drafts. It might execute multiple queries for this internally.
+As part of this orchestration additional events `ACTIVE_READ` and `DRAFT_READ` are triggered. They allow custom handlers to override reading of active entities or reading of drafts:
+
+| HTTP / OData request | Event constant name | Default implementation |
+| ----------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
+| GET | `CqnService.EVENT_READ` | Reads and merges data from active entities with their drafts. Internally triggers `ACTIVE_READ` and `DRAFT_READ`. |
+| n/a | `DraftService.EVENT_ACTIVE_READ` | Reads data from active entities. |
+| n/a | `DraftService.EVENT_DRAFT_READ` | Reads data from drafts. |
+
+::: tip
+`@Before` or `@After` handlers which modify queries or read data are best registered on the `READ` event.
+Events `ACTIVE_READ` or `DRAFT_READ` are preferrable for custom `@On` handlers of draft-enabled entities.
+:::
+
+By default queries executed internally by the `READ` event are optimized for performance. In certain scenarios queries will rely on the possibility of joining between tables of the active entity and drafts on the database.
+
+Active entity data and draft data is usually stored in tables on the same database schema. However, it is also possible to enable remote entities or entities stored in a different persistence for drafts. In that case set the property `cds.drafts.persistence` to `split` (default: `joint`). This enforces the following behavior:
+
+- Queries strictly separate active entities and drafts.
+- Queries to active entities don't contain draft-specific elements like `IsActiveEntity`.
+
+You can then delegate reading of active entities, for example to a remote S/4 system:
+
+```java
+@On(entity = MyRemoteDraftEnabledEntity_.CDS_NAME)
+public Result delegateToS4(ActiveReadEventContext context) {
+ return remoteS4.run(context.getCqn());
+}
+```
+
+> Note that this is only useful when also delegating `CREATE`, `UPDATE` and `DELETE` events, which only operate on active entities always, to the remote S/4 system as well.
+
+::: warning
+When setting `cds.drafts.persistence` to `split` only queries that are specified by the SAP Fiori draft orchestration are supported.
+:::
+
+## Editing Drafts
+
+When users edit a draft-enabled entity in the frontend, the following requests are sent to the CAP Java backend. As an effect, draft-specific events are triggered, as described in the following table. The draft-specific events are defined by the [DraftService](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/draft/DraftService.html) interface.
+
+::: tip
+Draft-enabled entities have an extra key `IsActiveEntity` by which you can access either the active entity or the draft (inactive entity).
+:::
+
+| HTTP / OData request | Event constant name | Default implementation |
+| -------------------------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
+| POST | `DraftService.EVENT_DRAFT_NEW` | Creates a new empty draft. Internally triggers `DRAFT_CREATE`. |
+| PATCH with key `IsActiveEntity=false` | `DraftService.EVENT_DRAFT_PATCH` | Updates an existing draft |
+| DELETE with key `IsActiveEntity=false` | `DraftService.EVENT_DRAFT_CANCEL` | Deletes an existing draft |
+| DELETE with key `IsActiveEntity=true` | `CqnService.EVENT_DELETE` | Deletes an active entity *and* the corresponding draft |
+| POST with action `draftPrepare` | `DraftService.EVENT_DRAFT_PREPARE` | Empty implementation |
+| POST with action `draftEdit` | `DraftService.EVENT_DRAFT_EDIT` | Creates a new draft from an active entity. Internally triggers `DRAFT_CREATE`. |
+| POST with action `draftActivate` | `DraftService.EVENT_DRAFT_SAVE` | Activates a draft and updates the active entity. Triggers an `CREATE` or `UPDATE` event on the affected entity. |
+| n/a | `DraftService.EVENT_DRAFT_CREATE` | Stores a new draft in the database. |
+
+You can use these events to add custom logic to the SAP Fiori draft flow, for example to interact with drafts or to validate user data.
+
+The following example registers a `@Before` handler to fill in default-values into a draft before the user starts editing:
+
+```java
+@Before
+public void prefillOrderItems(DraftNewEventContext context, OrderItems orderItem) {
+ // Pre-fill fields with default values
+}
+```
+
+The `DRAFT_CREATE` is an internal event that is not triggered by OData requests directly. It can be used to set default or calculated values on new drafts, regardless if they were created from scratch (`DRAFT_NEW` flow) or based on an existing active entity (`DRAFT_EDIT` flow).
+
+For more examples, see the [Bookshop sample application](https://github.com/SAP-samples/cloud-cap-samples-java/tree/master/srv/src/main/java/my/bookshop/handlers/AdminServiceHandler.java).
+
+## Activating Drafts
+
+When you finish editing drafts by pressing the *Save* button, a draft gets activated. That means, either a single `CREATE` or `UPDATE` event is triggered to create or update the active entity with all of its compositions through a deeply structured document. You can register to these events to validate the activated data.
+
+The following example shows how to validate user input right before an active entity gets created:
+
+```java
+@Before
+public void validateOrderItem(CdsCreateEventContext context, OrderItems orderItem) {
+ // Add validation logic
+}
+```
+
+During activation the draft data is deleted from the database. This happens before the active entity is created or updated within the same transaction.
+In case the create or update operation raises an error, the transaction is rolled back and the draft data is restored.
+
+## Working with Draft-Enabled Entities
+
+When deleting active entities that have a draft, the draft is deleted as well. In this case, a `DELETE` and `DRAFT_CANCEL` event are triggered.
+
+To read an active entity, send a `GET` request with key `IsActiveEntity=true`, for example:
+
+```http
+GET /v4/myservice/myentity(IsActiveEntity=true,ID=);
+```
+
+Likewise, to read the corresponding draft, call:
+
+```http
+GET /v4/myservice/myentity(IsActiveEntity=false,ID=);
+```
+
+To get all active entities, you could use a filter as illustrated by the following example:
+
+```http
+GET /v4/myservice/myentity?$filter=IsActiveEntity eq true
+```
+
+## Bypassing the SAP Fiori Draft Flow { #bypassing-draft-flow }
+
+It's possible to create and update data directly without creating intermediate drafts. For example, this is useful when prefilling draft-enabled entities with data or in general, when technical components deal with the API exposed by draft-enabled entities. To achieve this, use the following requests. You can register event handlers for the corresponding events to validate incoming data:
+
+| HTTP / OData request | Event constant name | Default implementation |
+| ----------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------- |
+| POST with `IsActiveEntity: true` in payload | `CqnService.EVENT_CREATE` | Creates the active entity |
+| PUT with key `IsActiveEntity=true` in URI | `CqnService.EVENT_CREATE` `CqnService.EVENT_UPDATE` | Creates or updates the active entity (full update) |
+| PATCH with key `IsActiveEntity=true` in URI | `CqnService.EVENT_UPDATE` | Creates or updates the active entity (sparse update) |
+
+These events have the same semantics as described in section [Handling CRUD events](./cqn-services/application-services#crudevents).
+
+## Draft Lock { #draft-lock }
+
+An entity with a draft is locked from being edited by other users until either the draft is saved or a timeout is hit (15 minutes by default). You can configure this timeout by the following application configuration property:
+
+```yaml
+cds.drafts.cancellationTimeout: 1h
+```
+
+You can turn off this feature completely by means of the application configuration property:
+
+```yaml
+cds.security.draftProtection.enabled: false
+```
+
+## Draft Garbage Collection { #draft-gc }
+
+Stale drafts are automatically deleted after a timeout (30 days default). You can configure the timeout with the following application configuration property:
+
+```yaml
+cds.drafts.deletionTimeout: 8w
+```
+
+In this example, the draft timeout is set to 8 weeks.
+
+This feature can be also turned off completely by setting the application configuration:
+
+```yaml
+cds.drafts.gc.enabled: false
+```
+
+::: tip
+To get notified when a particular draft-enabled entity is garbage collected, you can register an event handler on the `DRAFT_CANCEL` event.
+:::
+
+## Overriding SAP Fiori's Draft Creation Behaviour { #fioridraftnew}
+
+By default SAP Fiori triggers a POST request with an empty body to the entity collection to create a new draft.
+This behavior can be overridden [by implementing a custom action](./cqn-services/application-services#actions), which SAP Fiori will trigger instead.
+
+1. Define an action bound to the draft-enabled entity with an explicitly binding parameter typed with `many $self`.
+
+ This way, the action used to create a new draft is bound to the draft-enabled entity collection.
+
+1. Annotate the draft-enabled entity with `@Common.DraftRoot.NewAction: ''`.
+
+ This indicates to SAP Fiori that this action should be used when creating a new draft.
+
+1. Implement the action in Java.
+
+ The implementation of the action must trigger the `newDraft(CqnInsert)` method of the `DraftService` interface to create the draft. In addition, it must return the created draft entity.
+
+The following code summarizes all of these steps in an example:
+
+```cds
+service AdminService {
+ @odata.draft.enabled
+ @Common.DraftRoot.NewAction: 'AdminService.createDraft'
+ entity Orders as projection on my.Orders actions {
+ action createDraft(in: many $self, orderNo: String) returns Orders;
+ };
+}
+```
+
+```java
+@On(entity = Orders_.CDS_NAME)
+public void createDraft(CreateDraftContext context) {
+ Orders order = Orders.create();
+ order.setOrderNo(context.getOrderNo());
+ context.setResult(adminService.newDraft(Insert.into(Orders_.class).entry(order)).single(Orders.class));
+}
+```
+
+## Consuming Draft Services { #draftservices}
+
+If an [Application Service](cqn-services/application-services#application-services) is created based on a service definition, that contains a draft-enabled entity, it also implements the [DraftService](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/draft/DraftService.html) interface.
+This interface provides an API layer around the [draft-specific events](fiori-drafts#draftevents), and allows to create new draft entities, patch, cancel or save them, and put active entities back into edit mode.
+
+The Draft-Service-specific APIs only operate on entities in draft-mode. The CQN Query APIs (`run` methods) provided by any Application Service, operate on active entities only.
+However, there's one exception from this behavior, which is the `READ` event: When reading from a Draft Service, active entities and draft entities are both queried and the results are combined.
+
+::: warning
+Persistence Services aren't draft-aware. Use the respective Draft Service or Application Service, when running draft-aware queries.
+:::
+
+The following example, shows the usage of the Draft-Service-specific APIs:
+
+```java
+import static bookshop.Bookshop_.ORDERS;
+
+DraftService adminService = ...;
+// create draft
+Orders order = adminService.newDraft(Insert.into(ORDERS)).single(Orders.class);
+// set values
+order.setOrderNo("DE-123456");
+// patch draft
+adminService.patchDraft(Update.entity(ORDERS).data(order)
+ .where(o -> o.ID().eq(order.getId()).and(o.IsActiveEntity().eq(false))));
+// save draft
+CqnSelect orderDraft = Select.from(ORDERS)
+ .where(o -> o.ID().eq(order.getId()).and(o.IsActiveEntity().eq(false)));
+adminService.saveDraft(orderDraft);
+// read draft
+Orders draftOrder = adminService.run(orderDraft).single().as(Order.class);
+// put draft back to edit mode
+CqnSelect orderActive = Select.from(ORDERS)
+ .where(o -> o.ID().eq(order.getId()).and(o.IsActiveEntity().eq(true)));
+adminService.editDraft(orderActive, true);
+// read entities in draft mode and activated entities
+adminService.run(Select.from(ORDERS).where(o -> o.ID().eq(order.getId())));
+```
diff --git a/java/getting-started.md b/java/getting-started.md
index 5c532aa3f..6db47f3a2 100644
--- a/java/getting-started.md
+++ b/java/getting-started.md
@@ -98,10 +98,10 @@ mvn com.sap.cds:cds-maven-plugin:add -Dfeature=TINY_SAMPLE
### Add CloudFoundry target platform
-Following the "[Grow As You Go](../about/#grow-as-you-go)" principle, the generated CAP Java project doesn't contain support for Cloud Foundry as the target platform. To enhance your project with dependencies required for Cloud Foundry, execute the goal `addTargetPlatform` of the [CDS Maven plugin](./assets/cds-maven-plugin-site/addTargetPlatform-mojo.html){target="_blank"} using the following command:
+Following the "[Grow As You Go](../about/#grow-as-you-go)" principle, the generated CAP Java project doesn't contain support for Cloud Foundry as the target platform. To enhance your project with dependencies required for Cloud Foundry, execute the goal `add` of the [CDS Maven plugin](./assets/cds-maven-plugin-site/add-mojo.html){target="_blank"} using the following command:
```sh
-mvn com.sap.cds:cds-maven-plugin:addTargetPlatform -DtargetPlatform=cloudfoundry
+mvn com.sap.cds:cds-maven-plugin:add -Dfeature=CF
```
This command adds the following dependency to the pom.xml:
diff --git a/java/messaging.md b/java/messaging.md
index ecf7a8a37..6fa0c7fd2 100644
--- a/java/messaging.md
+++ b/java/messaging.md
@@ -542,7 +542,7 @@ cds:
```
:::
-[Learn more about SAP Event Mesh configuration options.](https://help.sap.com/doc/75c9efd00fc14183abc4c613490c53f4/Cloud/en-US/rest-management-messaging.html#_queuep){.learn-more}
+[Learn more about SAP Event Mesh configuration options.](https://hub.sap.com/api/SAPEventMeshDefaultManagementAPIs/path/putQueue){.learn-more}
@@ -680,6 +680,44 @@ private void handleError(MessagingErrorEventContext ctx) {
}
```
+In a multi-tenant setup with several microservices, messages of a tenant not yet subscribed to the own microservice would be already received from the message queue. In this case, the message cannot be processed for the tenant because the tenant context is not yet available. By default, the standard error handler still acknowledges the message to prevent it from getting stuck in the message sequence. To change this behavior, the custom error handler from the example above can be extended by checking the exception type of the unknown tenant.
+
+
+```java
+@On(service = "messaging")
+private void handleError(MessagingErrorEventContext ctx) {
+
+ String errorCode = ctx.getException().getErrorStatus().getCodeString();
+ if (errorCode.equals(CdsErrorStatuses.NO_ON_HANDLER.getCodeString()) ||
+ errorCode.equals(CdsErrorStatuses.INVALID_DATA_FORMAT.getCodeString())) {
+ // error handling for infrastructure error
+ ctx.setResult(false); // no acknowledgement
+
+ } else if (errorCode.equals(CdsErrorStatuses.TENANT_NOT_EXISTS.getCodeString())) {
+ // error handling for unknown tenant context
+
+ // tenant of the received message
+ String tenant = ctx.getTenant();
+
+ // received message
+ Map headers = ctx.getMessageHeaders();
+ Map message = ctx.getMessageData();
+
+ ctx.setResult(true); // acknowledge
+ } else {
+ // error handling for application errors
+
+ // how to access the event context of the raised exception:
+ // ctx.getException().getEventContexts().stream().findFirst().ifPresent(e -> {
+ // String event = e.getEvent());
+ // String payload = e.get("data"));
+ // });
+
+ ctx.setResult(true); // acknowledge
+ }
+}
+```
+
::: warning _β Warning_
The way how unsuccessfully delivered messages are treated, fully depends on the messaging broker. Please check in section [Acknowledgement Support](#acknowledgement-support) whether the messaging broker you are using is suitable for your error handler implementation.
:::
diff --git a/java/multitenancy-classic.md b/java/multitenancy-classic.md
new file mode 100644
index 000000000..ce6d5abbd
--- /dev/null
+++ b/java/multitenancy-classic.md
@@ -0,0 +1,950 @@
+---
+synopsis: >
+ CAP applications can be run as software as a service (SaaS). That means, multiple customers (subscriber tenants) can use the application at the same time in an isolated manner.
+ This section explains how to configure multitenancy for the CAP Java.
+status: released
+uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/9186ed9ab00842e1a31309ff1be38792.html
+---
+
+# Multitenancy (Classic) { #multitenancy-classic}
+
+{{ $frontmatter.synopsis }}
+
+::: warning
+The multitenancy services (`@sap/cds-mtx`) described in this chapter are in maintenance mode and only supported until CAP Java 2.x.
+If you start a new multitenancy project, it's highly recommended to make use of [Multitenancy](multitenancy) based on CAP Java 3.x and streamlined MTX (`@sap/cds-mtxs`).
+:::
+
+## Overview
+
+For a general overview on this topic, see the [Multitenancy guide](../guides/multitenancy/?impl-variant=java).
+
+In CAP Java, the Node.js based [*cds-mtx* module](../guides/multitenancy/?impl-variant=java) is reused to handle tenant provisioning. This reuse has the following implications:
+
+- Java applications need to run and maintain the *cds-mtx* module as a sidecar application (called *MTX sidecar* in this documentation). The following sections describe the setup as a [Multitarget Application](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/d04fc0e2ad894545aebfd7126384307c.html) using a [Multitarget Application Development Descriptor](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/c2d31e70a86440a19e47ead0cb349fdb.html) (*mta.yaml*) file. It can be packaged by means of the [MTA Build Tool](https://sap.github.io/cloud-mta-build-tool) and deployed to the SAP BTP by means of the Deploy Service.
+- Multitenant CAP Java applications automatically expose the tenant provisioning API called by the SaaS Provisioning service so that [custom logic during tenant provisioning](#custom-logic) can be written in Java.
+
+The following figure describes the basic setup:
+
+
+
+
+
+## Maven Dependencies
+
+Multitenancy support is available as a so called optional [application feature](developing-applications/building#starter-bundles#application-features) of the CAP Java SDK. It's already included when you use the `cds-starter-cloudfoundry` dependency. Otherwise, you can add the following Maven dependency to apply the feature:
+
+```xml
+
+ com.sap.cds
+ cds-feature-mt
+
+```
+
+::: tip
+When you add this dependency to your project, it becomes active when certain conditions are fulfilled, for example, [when your application is deployed to SAP BTP](#required-services-mt). This condition check lets you test your application locally without multitenancy turned on.
+:::
+
+## Tenant Subscription Events { #custom-logic }
+
+The [SaaS Provisioning service](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/ed08c7dcb35d4082936c045e7d7b3ecd.html) (`saas-registry`) in SAP BTP sends [specific requests](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/ff540477f5404e3da2a8ce23dcee602a.html) to applications when tenants are subscribed or unsubscribed. For these requests, the CAP Java SDK internally generates CAP events on the technical service [`MtSubscriptionService`](https://www.javadoc.io/doc/com.sap.cds/cds-feature-mt/latest/com/sap/cds/services/mt/MtSubscriptionService.html).
+
+[For a general introduction to CAP events, see Service Provisioning API.](event-handlers/){.learn-more}
+
+Register event handlers for the following CAP events to add custom logic for requests sent by the SaaS Provisioning service. Each event passes a special type of `EventContext` object to the event handler method and provides event-specific information:
+
+| Event Name | Event Context | Use Case |
+| ------------------------ | ------------------------------------------------------------------------------------- | --------------- |
+| `EVENT_SUBSCRIBE` | [MtSubscribeEventContext](https://www.javadoc.io/doc/com.sap.cds/cds-feature-mt/latest/com/sap/cds/services/mt/MtSubscribeEventContext.html) | Add a tenant |
+| `EVENT_UNSUBSCRIBE` | [MtUnsubscribeEventContext](https://www.javadoc.io/doc/com.sap.cds/cds-feature-mt/latest/com/sap/cds/services/mt/MtUnsubscribeEventContext.html) | Remove a tenant |
+| `EVENT_GET_DEPENDENCIES` | [MtGetDependenciesEventContext](https://www.javadoc.io/doc/com.sap.cds/cds-feature-mt/latest/com/sap/cds/services/mt/MtGetDependenciesEventContext.html) | Dependencies |
+
+
+
+You only need to register event handlers to override the default behavior.
+
+Default behaviors:
+
+- A new tenant-specific database container is created through the [Service Manager](https://help.sap.com/docs/SERVICEMANAGEMENT/09cc82baadc542a688176dce601398de/3a27b85a47fc4dff99184dd5bf181e14.html) during subscription.
+- A tenant-specific database container *isn't* deleted during unsubscription.
+
+The following sections describe how to register to these events in more detail.
+
+## Subscribe Tenant
+
+Subscription events are generated when a new tenant is added.
+By default, subscription creates a new database container for a newly subscribed tenant.
+
+### Synchronous Tenant Subscription
+
+By default an `EVENT_SUBSCRIBE` event is sent when a tenant is added.
+
+The following example shows how to register to this event:
+
+```java
+package com.sap.cds.demo.spring.handler;
+
+import org.springframework.stereotype.Component;
+
+import com.sap.cds.services.handler.EventHandler;
+import com.sap.cds.services.handler.annotations.Before;
+import com.sap.cds.services.handler.annotations.ServiceName;
+import com.sap.cds.services.mt.MtSubscriptionService;
+import com.sap.cds.services.mt.MtUnsubscribeEventContext;
+
+@Component
+@ServiceName(MtSubscriptionService.DEFAULT_NAME)
+public class SubscriptionHandler implements EventHandler {
+
+ @Before(event = MtSubscriptionService.EVENT_SUBSCRIBE)
+ public void beforeSubscription(MtSubscribeEventContext context) {
+ // Activities before tenant database container is created
+ }
+
+}
+```
+
+
+To send notifications when a subscription was successful, you could register an `@After` handler:
+
+```java
+@After(event = MtSubscriptionService.EVENT_SUBSCRIBE)
+public void afterSubscription(MtSubscribeEventContext context) {
+ // For example, send notification, β¦
+}
+```
+
+
+
+### Returning a Database ID
+
+When you've registered exactly one SAP HANA instance in your SAP BTP space, a new tenant-specific database container is created automatically. However, if you've registered more than one SAP HANA instance in your SAP BTP space, you have to pass the target database ID for the new database container in a customer handler, as illustrated in the following example:
+
+```java
+@Before(event = MtSubscriptionService.EVENT_SUBSCRIBE)
+public void beforeSubscription(MtSubscribeEventContext context) {
+ context.setInstanceCreationOptions(
+ new InstanceCreationOptions().withProvisioningParameters(
+ Collections.singletonMap("database_id", "")));
+}
+```
+
+### Returning a Custom Application URL
+
+The following example shows how to return a custom application URL that is shown in SAP BTP Cockpit:
+
+```java
+@After(event = MtSubscriptionService.EVENT_SUBSCRIBE)
+public void afterSubscribe(MtSubscribeEventContext context) {
+ if (context.getResult() == null) {
+ context.setResult(
+ "https://" +
+ context.getSubscriptionPayload().subscribedSubdomain +
+ ".myapp.com");
+ }
+}
+```
+
+By default, the application URL is constructed by configuration as described in [Wiring It Up](#binding-it-together).
+
+### Returning Dependencies
+
+The event `EVENT_GET_DEPENDENCIES` fires when the SaaS Provisioning calls the [`getDependencies` callback](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/ff540477f5404e3da2a8ce23dcee602a.html). Hence, if your application consumes any reuse services provided by SAP, you must implement the `EVENT_GET_DEPENDENCIES` to return the service dependencies of the application. The callback must return a `200` response code and a JSON file with the dependent services' `appName` and `appId`, or just the `xsappname`.
+
+::: tip
+The `xsappname` of an SAP reuse service that is bound to your application can be found as part of the `VCAP_SERVICES` JSON structure under the path `VCAP_SERVICES..credentials.xsappname`.
+:::
+
+The following example shows this in more detail:
+
+```java
+import com.sap.cloud.mt.subscription.json.ApplicationDependency;
+
+@Value("${vcap.services..credentials.xsappname}")
+private String xsappname;
+
+@On(event = MtSubscriptionService.EVENT_GET_DEPENDENCIES)
+public void onGetDependencies(MtGetDependenciesEventContext context) {
+ ApplicationDependency dependency = new ApplicationDependency();
+ dependency.xsappname = xsappname;
+ List dependencies = new ArrayList<>();
+ dependencies.add(dependency);
+ context.setResult(dependencies);
+}
+```
+
+## Unsubscribe Tenant
+
+Unsubscription events are generated, when a tenant is offboarded. By default, the tenant-specific database container is *not* deleted during offboarding. You can change this behavior by registering a custom event handler as illustrated in the following examples.
+
+### Synchronous Tenant Unsubscription
+
+By default an `EVENT_UNSUBSCRIBE` is sent when a tenant is removed. The following example shows how to add custom logic for this event:
+
+```java
+@Before(event = MtSubscriptionService.EVENT_UNSUBSCRIBE)
+public void beforeUnsubscribe(MtUnsubscribeEventContext context) {
+ // Activities before offboarding
+}
+```
+
+You can also register an `@After` handler, for example to notify when removal is finished:
+
+```java
+@After(event = MtSubscriptionService.EVENT_UNSUBSCRIBE)
+public void afterUnsubscribe(MtUnsubscribeEventContext context) {
+ // Notify offboarding finished
+}
+```
+
+
+
+### Deleting Tenant Containers During Tenant Unsubscription
+
+By default, tenant-specific database containers aren't deleted during removal. However, you can register a customer handler change this behavior. For example:
+
+```java
+@Before(event = MtSubscriptionService.EVENT_UNSUBSCRIBE)
+public void beforeUnsubscribe(MtUnsubscribeEventContext context) {
+ // Trigger deletion of database container of offboarded tenant
+ context.setDelete(true);
+}
+```
+
+## Configuring the Required Services { #required-services-mt}
+
+To enable multitenancy on SAP BTP, three services are involved:
+
+- XSUAA
+- Service Manager
+- SaaS Provisioning service (`saas-registry`)
+
+Only when these services are bound to your application, the multitenancy feature is turned on. You can either create and configure these services manually. See section [Developing Multitenant Applications in SAP BTP, Cloud Foundry Environment](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/5e8a2b74e4f2442b8257c850ed912f48.html) for more details. The following sections describe how to configure and bind these services by means of an *mta.yaml* file.
+
+### XSUAA { #xsuaa-mt-configuration }
+
+A special configuration of an XSUAA service instance is required to enable authorization between the SaaS Provisioning service, CAP Java application, and MTX sidecar.
+
+The service can be configured in the *mta.yaml* by adding an `xsuaa` resource as follows:
+
+```yaml
+resources:
+ [β¦]
+ - name: xsuaa
+ type: com.sap.xs.uaa
+ parameters:
+ service-plan: application
+ path: ./xs-security.json
+ config:
+ xsappname:
+```
+
+Choose a value for property `xsappname` that is unique globally.
+
+Also, you have to create an [Application Security Descriptor (*xs-security.json*)](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/517895a9612241259d6941dbf9ad81cb.html) file, which must include two scopes:
+
+- `mtcallback`
+- `mtdeployment`
+
+> You can also use custom scope names by configuring them. Use the following application configuration properties:
+>
+> - mtcallback: `cds.multitenancy.security.subscription-scope`
+> - mtdeployment: `cds.multitenancy.security.deployment-scope`
+
+The `mtcallback` scope is required by the onboarding process. The `mtdeployment` scope is required to redeploy database artifacts at runtime.
+
+An example *xs-security.json* file looks like this:
+
+```json
+{
+ "xsappname": "",
+ "tenant-mode": "shared",
+ "scopes": [
+ {
+ "name": "$XSAPPNAME.mtcallback",
+ "description": "Multi Tenancy Callback Access",
+ "grant-as-authority-to-apps": [
+ "$XSAPPNAME(application, sap-provisioning, tenant-onboarding)"
+ ]
+ },
+ {
+ "name": "$XSAPPNAME.mtdeployment",
+ "description": "Scope to trigger a re-deployment of the database artifacts"
+ }
+ ],
+ "authorities": [
+ "$XSAPPNAME.mtdeployment"
+ ]
+}
+```
+
+In this example, the `grant-as-authority-to-apps` section is used to grant the `mtcallback` scope to the applications *sap-provisioning* and *tenant-onboarding*. These are services provided by SAP BTP involved in the onboarding process.
+
+It isn't necessary to have the security configuration in a separate file. It can also be added to the *mta.yaml* file directly.
+
+::: warning *β Warning*
+The `mtcallback` and `mtdeployment` scopes **must not be exposed** to any business user, for example, using a role template. Else a malicious user could update or even delete the artifacts of arbitrary tenants. In addition, if you implement a service broker in order to expose your service API for (technical) users of SaaS tenants, you must ensure that both scopes **cannot be consumed as authorities** in cloned service instances created by clients. To achieve that, set `authorities-inheritance: false`. It is **strongly recommended** to explicitly enumerate all authorities that should be exposed in the the broker configuration (allow-list).
+:::
+
+### Service Manager
+
+A service instance of the [Service Manager](https://help.sap.com/docs/SERVICEMANAGEMENT/09cc82baadc542a688176dce601398de/3a27b85a47fc4dff99184dd5bf181e14.html) (`service-manager`) is required that the CAP Java SDK can create database containers per tenant at application runtime. It doesn't require special parameters and can be added as a resource in *mta.yaml* as follows:
+
+```yaml
+resources:
+ [β¦]
+ - name: service-manager
+ type: org.cloudfoundry.managed-service
+ parameters:
+ service: service-manager
+ service-plan: container
+```
+
+### SaaS Provisioning Service (saas-registry) { #saas-registry }
+
+A `saas-registry` service instance is required to make your application known to the SAP BTP Provisioning Service and to register the endpoints that should be called when tenants are added or removed. The service can be configured as a resource in *mta.yaml* as follows. See section [Register the Multitenant Application to the SaaS Provisioning Service](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/3971151ba22e4faa9b245943feecea54.html) for more details.
+
+```yaml
+resources:
+ [β¦]
+ - name: saas-registry
+ type: org.cloudfoundry.managed-service
+ parameters:
+ service: saas-registry
+ service-plan: application
+ config:
+ appName:
+ xsappname:
+ appUrls:
+ getDependencies: ~{srv/url}/mt/v1.0/subscriptions/dependencies
+ onSubscription: ~{srv/url}/mt/v1.0/subscriptions/tenants/{tenantId}
+ requires:
+ - name: srv
+```
+
+It's required to configure the parameters:
+
+- `appName`: Choose an appropriate application display name.
+- `xsappname`: Use the value for `xsappname` you configured at your [UAA service instance](#xsuaa-mt-configuration).
+- `appUrls`: Configure the callback URLs used by the SaaS Provisioning service to get the dependencies of the application and to trigger a subscription. In the above example, the property `~{srv/url}` that is provided by the `srv` module is used. See section [Wiring It Up](#binding-it-together) for more details. If you use different module and property names for your CAP Java backend module, you have to adapt these properties here accordingly.
+
+## Adding the MTX Sidecar Application { #mtx-sidecar-server }
+
+This section describes how to use the `cds-mtx` Node.js module and add the MTX sidecar microservice to the *mta.yaml* file.
+
+In a dedicated project subfolder named *mtx-sidecar*, create a Node.js start script in a file named *server.js* to bootstrap the `cds-mtx` library:
+
+```js
+const app = require('express')();
+const cds = require('@sap/cds');
+
+const main = async () => {
+ await cds.connect.to('db');
+ const PORT = process.env.PORT || 4004;
+ await cds.mtx.in(app);
+ app.listen(PORT);
+}
+
+main();
+```
+
+::: tip
+By default, this script implements authorization and checks for the scope `mtcallback`. If you use a custom scope name for requests issued by the SaaS Provisioning Service in your application security descriptor (*xs-security.json*), you have to configure the custom scope name at the MTX sidecar as well. Use the environment variable `CDS_MULTITENANCY_SECURITY_SUBSCRIPTIONSCOPE`, for example, by specifying it in the *mta.yaml* file.
+:::
+
+To define the dependencies and start command, also create a file *package.json* like this:
+
+```json
+{
+ "name": "deploy",
+ "engines": {
+ "node": ">=12"
+ },
+ "scripts": {
+ "start": "node server.js"
+ }
+}
+```
+
+Next, add the required dependencies:
+
+```sh
+npm add @sap/cds @sap/cds-mtx @sap/xssec hdb express
+```
+
+Because the MTX sidecar will build the CDS model, you need to configure the build by means of two *.cdsrc.json* files.
+
+The first *.cdsrc.json* file goes into the root folder of your project and specifies from which location the CDS files should be collected. The following example demonstrates this:
+
+```json
+{
+ "build": {
+ "target": ".",
+ "tasks": [
+ {
+ "for": "java-cf"
+ },
+ {
+ "for": "mtx",
+ "src": ".",
+ "dest": "mtx-sidecar"
+ },
+ {
+ "for": "hana"
+ }
+ ]
+ },
+ "requires": {
+ "db": {
+ "kind": "hana-mt"
+ }
+ },
+ "odata": {
+ "version": "v4"
+ }
+}
+```
+
+::: tip
+You only need to change this configuration if you named your project folders, `app`, `db`, `srv`, and `mtx-sidecar` differently.
+:::
+
+A detailed description of this configuration file can be found in section [Build Configuration](../guides/deployment/custom-builds#build-config). In the following, you find a short summary of this example:
+
+The `build` section defines the build `tasks` that should be executed. Three build tasks are defined in this example:
+
+| Task | Description |
+| --------- | ------------------------------------------------------------------------------- |
+| `java-cf` | Generates *csn.json* and EDMX files |
+| `mtx` | Collects *.cds* files to copy to *mtx-sidecar* directory, generates *i18n.json* |
+| `hana` | Generates SAP HANA artifacts |
+
+In the previous example, the `options` section specifies the source directories for each build task.
+
+::: tip
+The `hana` build task is optional because the SAP HANA artifacts are also generated by the *mtx-sidecar* directly. However, the generated SAP HANA artifacts enable you to test your application in a single tenant scenario.
+:::
+
+The second *.cdsrc.json* file goes into the *mtx-sidecar* directory. The following example demonstrates this:
+
+```json
+{
+ "hana": {
+ "deploy-format": "hdbtable"
+ },
+ "build": {
+ "tasks": [
+ {
+ "for": "hana"
+ },
+ {
+ "for": "java-cf"
+ }
+ ]
+ },
+ "odata": {
+ "version": "v4"
+ },
+ "requires": {
+ "db": {
+ "kind": "hana-mt",
+ },
+ "auth": {
+ "kind": "xsuaa"
+ },
+ "multitenancy": true
+ }
+}
+```
+
+::: tip
+You only need to change this configuration if you named your project folders, `app`, `db`, `srv`, and `mtx-sidecar` differently.
+:::
+
+::: warning
+If you have configured a location for your i18n files as described in the [Localization Section](../guides/i18n#where-to-place-text-bundles), please make sure to add the same CDS configuration in both, the *.cdsrc.json* of the SaaS application and in the *.cdsrc.json* of the `mtx-sidecar`.
+:::
+
+In this file, the `requires` section configures the service instances that should be used by the *mtx-sidecar*. In this case, it's an instance of the UAA Service, to enable authentication and authorization, as well as the Service Manager, that enables multitenancy.
+
+Now, add the `mtx-sidecar` module to your *mta.yaml* file:
+
+```yaml
+modules:
+ [β¦]
+ - name: mtx-sidecar
+ type: nodejs
+ path: mtx-sidecar
+ parameters:
+ memory: 256M
+ disk-quota: 512M
+ requires:
+ - name: xsuaa
+ - name: service-manager
+ provides:
+ - name: mtx-sidecar
+ properties:
+ url: ${default-url}
+```
+
+The `mtx-sidecar` module requires the XSUAA and Service Manager services. Also you need to provide its URL to be able to configure the URL in the service module as shown in the previous *mta.yaml*. The authentication works through token validation.
+
+
+
+## Wiring It Up { #binding-it-together }
+
+To bind the previously mentioned services and the MTX sidecar to your CAP Java application, you could use the following example of the `srv` module in the *mta.yaml* file:
+
+```yaml
+modules:
+ [β¦]
+ - name: srv
+ type: java
+ path: srv
+ parameters:
+ [β¦]
+ requires:
+ - name: service-manager
+ - name: xsuaa
+ - name: mtx-sidecar
+ properties:
+ CDS_MULTITENANCY_SIDECAR_URL: ~{url}
+ - name: app
+ properties:
+ CDS_MULTITENANCY_APPUI_URL: ~{url}
+ CDS_MULTITENANCY_APPUI_TENANTSEPARATOR: "."
+ provides:
+ - name: srv
+ properties:
+ url: '${default-url}'
+```
+
+The environment variable `CDS_MULTITENANCY_SIDECAR_URL` of the `srv` module is internally mapped to property `cds.multitenancy.sidecar.url`. This URL is required by the runtime to connect to the [MTX Sidecar application](#mtx-sidecar-server) and it's derived from property `url` of the mtx-sidecar [module](#mtx-sidecar-server) (Note that `${default-url}` is a placeholder for the own URL).
+Similarly, `CDS_MULTITENANCY_APPUI_URL` configures the URL that is shown in the SAP BTP Cockpit. Usually it's pointing to the app providing the UI, which is the module `app` in this example.
+
+As value for `CDS_MULTITENANCY_APPUI_TENANTSEPARATOR` only `"."` is supported at the moment. The actual URL shown in the SAP BTP Cockpit is then composed of:
+
+```txt
+https://.
+```
+
+
+
+## Adding Logging Service Support { #app-log-support }
+
+Logging service support gives you the capability to observe properly correlated requests between the different components of your CAP application in Kibana. This is especially useful for `multi-tenant aware applications` that use the `MTX sidecar`.
+
+As described in the section [Observability > Logging](./operating-applications/observability#logging-service), in order to enable the Cloud Foundry `application-logs` service support with CAP Java SDK, it's recommended to use the [cf-java-logging-support](https://github.com/SAP/cf-java-logging-support). Information about configuration options is provided there, as well.
+
+### Adding the Service Bindings
+
+Aside from that, a service binding to the `application-logs` service in the Cloud Foundry environment is required. This can be set up manually with the SAP BTP cockpit for both, the CAP application and the MTX sidecar, or more easily with an `mta` deployment.
+
+The *mta.yaml* file needs a new resource definition for the `application-logs` service which is required by both the `srv` module of the CAP Java application and the `mtx-sidecar` module. Building and deploying from this manifest then creates the necessary `application-logs` service instance if not existing yet and the `service bindings`:
+
+```yaml
+modules:
+ [β¦]
+ - name: srv
+ type: java
+ path: srv
+ parameters:
+ [β¦]
+ requires:
+ [β¦]
+ - name: cf-logging
+ [β¦]
+ provides:
+ - name: srv
+ properties:
+ url: '${default-url}'
+ [β¦]
+ - name: sidecar
+ type: nodejs
+ path: mtx-sidecar
+ parameters:
+ [β¦]
+ requires:
+ [β¦]
+ - name: cf-logging
+ [β¦]
+ provides:
+ - name: sidecar
+ properties:
+ url: ${default-url}
+ [β¦]
+resources:
+ [β¦]
+ - name: cf-logging
+ type: org.cloudfoundry.managed-service
+ parameters:
+ service: application-logs
+ service-plan: lite
+ [β¦]
+```
+
+::: tip
+In our example, we use the service-plan `lite` of the `application-logs` service, but you might require one with larger quota limits.
+:::
+
+::: tip
+Complete examples for *mta.yaml* files can be found in the [CAP Java bookshop samples](https://github.com/SAP-samples/cloud-cap-samples-java/).
+:::
+
+### Configuring the MTX Sidecar Application
+
+To properly correlate requests in the `mtx-sidecar`, a `correlate()` function needs to be added to the `Express` app that acts as a middleware that either reads the `correlation id` from the request headers if provided or generates a new one if not.
+
+One way to do so, is to modify the Node.js start script `server.js`, that was introduced in [Adding the MTX Sidecar Application](#mtx-sidecar-server), as follows:
+
+```js
+const app = require('express')();
+const cds = require('@sap/cds');
+
+const main = async () => {
+ app.use(defaults.correlate);
+
+ await cds.connect.to('db');
+ const PORT = process.env.PORT || 4004;
+
+ await cds.mtx.in(app);
+ app.listen(PORT);
+}
+
+const defaults = {
+ get correlate() {
+ return (req, res, next) => {
+ const id = req.headers['x-correlation-id'] || req.headers['x-correlationid']
+ || req.headers['x-request-id'] || req.headers['x-vcap-request-id']
+ || cds.utils.uuid()
+ // new intermediate cds.context, if necessary
+ cds.context = { id }
+ // guarantee x-correlation-id going forward and set on res
+ req.headers['x-correlation-id'] = id
+ res.set('x-correlation-id', id)
+ // guaranteed access to cds.context._.req -> REVISIT
+ if (!cds.context._) cds.context._ = {}
+ if (!cds.context._.req) cds.context._.req = req
+ next()
+ }
+ }
+}
+
+main();
+```
+
+The final piece of configuration required for the MTX sidecar is to enable the Kibana formatter feature.
+
+The following object literal needs be added to the json object within the *package.json* file in the *mtx-sidecar* subfolder of your CAP Java application:
+
+```json
+"cds": {
+ "features": {
+ "kibana_formatter": true
+ }
+}
+```
+
+::: tip
+For the Kibana formatter feature it is recommended to use *@sap/cds* in version *5.4.3 or higher*, *@sap/cds-mtx* in version *2.2.0 or higher* and *node* in version *16.2.0 or higher*.
+:::
+
+### Correlated Application Logs
+
+With a successful deployment of the CAP application with all the previously mentioned configuration in place, application logs from both, the `srv` and `mtx sidecar` modules, will be properly correlated by their `correlation id`.
+
+This can easily be seen in `Kibana`, which is part of the ELK (*Elasticsearch/Logstash/Kibana*) stack on Cloud Foundry and available by default with the `application-logs` service:
+
+
+
+
+
+## Database Schema Update { #database-update }
+
+When shipping a new application version with an updated CDS model, the database schema for each subscribed tenant needs an update. The database schema update needs to be triggered explicitly, as described in the following sections.
+
+When the database schema update is triggered, the following CAP events are sent. By registering custom handlers for these events, you can add custom logic to influence the deployment process. By default, the CAP Java SDK notifies the *MTX Sidecar* to perform any schema upgrade if necessary.
+
+| Event Name | Event Context |
+| --------------------------- | ----------------------------------------------------------------------------------------- |
+| `EVENT_ASYNC_DEPLOY` | [MtAsyncDeployEventContext](https://www.javadoc.io/doc/com.sap.cds/cds-feature-mt/latest/com/sap/cds/services/mt/MtAsyncDeployEventContext.html) |
+| `EVENT_ASYNC_DEPLOY_STATUS` | [MtAsyncDeployStatusEventContext](https://www.javadoc.io/doc/com.sap.cds/cds-feature-mt/latest/com/sap/cds/services/mt/MtAsyncDeployStatusEventContext.html) |
+
+
+It's often desired to update the whole service in a zero downtime manner. This section doesn't deal with the details about updating a service productively, but describes tool support the CAP Java SDK offers to update database schemas.
+
+The following sections describe how to trigger the database schema upgrade for tenants.
+
+### Deploy Endpoint { #deploy-endpoint }
+
+When multitenancy is configured, the CAP Java SDK exposes a REST endpoint to update database schemata.
+
+::: warning *β Warning*
+You must use the scope `mtdeployment` for the following requests!
+:::
+
+#### Deployment Request
+
+Send this request when a new version of your application with an updated database schema was deployed. This call triggers updating the persistence of each tenant.
+
+##### Route
+
+```http
+POST /mt/v1.0/subscriptions/deploy/async
+```
+
+::: tip
+This is the default endpoint. One or more endpoints might differ if you configure different endpoints through properties.
+:::
+
+##### Body
+
+The `POST` request must contain the following body:
+
+```json
+{
+ "tenants": [ "all" ]
+}
+```
+
+Alternatively, you can also update single tenants:
+
+```json
+{
+ "tenants": ["", "", β¦]
+}
+```
+
+##### Response
+
+The deploy endpoint is asynchronous, so it returns immediately with status code `202` and JSON structure containing a `jobID` value:
+
+```json
+{
+ "jobID": ""
+}
+```
+
+#### Job Status Request
+
+You can use this `jobID` to check the progress of the operation by means of the following REST endpoint:
+
+##### Route
+
+```http
+GET /mt/v1.0/subscriptions/deploy/async/status/ HTTP/1.1
+```
+
+##### Response
+
+The server responds with status code `200`. During processing, the response looks like:
+
+```json
+{
+ "error": null,
+ "status": "RUNNING",
+ "result": null
+}
+```
+
+Once a job is finished, the collective status is reported like this:
+
+```json
+{
+ "error": null,
+ "status": "FINISHED",
+ "result": {
+ "tenants": {
+ "": {
+ "status": "SUCCESS",
+ "message": "",
+ "buildLogs": ""
+ },
+ "": {
+ "status": "FAILURE",
+ "message": "",
+ "buildLogs": ""
+ }
+ }
+ }
+}
+```
+
+::: tip
+Logs are persisted for a period of 30 minutes before they get deleted automatically. If you're requesting the job status after the 30-minute period expired, you get a *404 Not Found* response.
+:::
+
+
+
+### Deploy Main Method
+
+As an alternative to calling the [deploy REST endpoints](#deploy-endpoint), the CAP Java SDK also offers a `main` method in the class `com.sap.cds.framework.spring.utils.Deploy` that can be called from the command line while the CAP Java application is still stopped. This way, you can run the database deployment for all tenants before you start a new version of the Java application. This prevents new application code to access database artifacts that aren't yet deployed.
+
+::: warning
+While the CAP Java backend might be stopped when you call this method, the *MTX Sidecar* application must be running!
+:::
+
+
+
+This synchronization can also be automated, for example using [Cloud Foundry Tasks](https://docs.cloudfoundry.org/devguide/using-tasks.html) on SAP BTP and [Module Hooks](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/b9245ba90aa14681a416065df8e8c593.html) in your MTA.
+
+The `main` method takes an optional list of tenant IDs as input arguments. If tenant IDs are specified, only these tenants are updated. If no input parameters are specified, all tenants are updated. The method waits until all deployments are finished and then prints the result.
+
+The method returns the following exit codes
+
+| Exit Code | Result |
+| --------- | ------------------------------------------------------------------------------------------------ |
+| 0 | All tenants updated successfully. |
+| 1 | Failed to update at least one tenant. Re-run the procedure to make sure that all tenants are updated. |
+
+To run this method locally, use the following command where `` is the one of your application:
+
+::: code-group
+
+```sh [>= Spring Boot 3.2.0]
+java -cp -Dloader.main=com.sap.cds.framework.spring.utils.Deploy org.springframework.boot.loader.launch.PropertiesLauncher [] β¦ []
+```
+
+```sh [< Spring Boot 3.2.0]
+java -cp -Dloader.main=com.sap.cds.framework.spring.utils.Deploy org.springframework.boot.loader.PropertiesLauncher [] β¦ []
+```
+
+:::
+
+In the SAP BTP, Cloud Foundry environment it can be tricky to construct such a command. The reason is, that the JAR file is extracted by the Java Buildpack and the place of the Java executable isn't easy to determine. Also the place differs for different Java versions. Therefore, we recommend to adapt the start command that is generated by the buildpack and run the adapted command:
+
+::: code-group
+
+```sh [>= Spring Boot 3.2.0]
+sed -i 's/org.springframework.boot.loader.launch.JarLauncher/org.springframework.boot.loader.launch.PropertiesLauncher/g' /home/vcap/staging_info.yml && sed -i 's/-Dsun.net.inetaddr.negative.ttl=0/-Dsun.net.inetaddr.negative.ttl=0 -Dloader.main=com.sap.cds.framework.spring.utils.Deploy/g' /home/vcap/staging_info.yml && jq -r .start_command /home/vcap/staging_info.yml | bash
+```
+
+```sh [< Spring Boot 3.2.0]
+sed -i 's/org.springframework.boot.loader.JarLauncher/org.springframework.boot.loader.PropertiesLauncher/g' /home/vcap/staging_info.yml && sed -i 's/-Dsun.net.inetaddr.negative.ttl=0/-Dsun.net.inetaddr.negative.ttl=0 -Dloader.main=com.sap.cds.framework.spring.utils.Deploy/g' /home/vcap/staging_info.yml && jq -r .start_command /home/vcap/staging_info.yml | bash
+```
+
+```sh [Java 8]
+sed -i 's/org.springframework.boot.loader.JarLauncher/-Dloader.main=com.sap.cds.framework.spring.utils.Deploy org.springframework.boot.loader.PropertiesLauncher/g' /home/vcap/staging_info.yml && jq -r .start_command /home/vcap/staging_info.yml | bash
+```
+
+:::
+
+## Developing Multitenant CAP Applications
+
+### Local Development
+
+A multitenant CAP application can still be started and tested locally, for example with SQLite. In this case,
+the CAP Java SDK simply disables the multitenancy feature (as there is no Service Manager service binding present) to enable local testing of the general business logic.
+
+Another option is to access cloud services from the local development machine (hybrid scenario). You can decide whether you want to access just one fixed SAP HANA service binding or access all available SAP HANA service bindings that were created through the Service Manager binding, which is described by the following sections.
+
+#### Static SAP HANA Binding
+
+For the static case, just copy the credentials of the SAP HANA service binding you want to use into the *default-env.json*. You can, for example, see all application-managed service instances in the SAP BTP Cockpit. The app behaves like in the single tenant case.
+
+#### Service Manager Binding
+
+If you want to test multitenancy locally, just copy the complete Service Manager binding into the *default-env.json*. If you have extensibility enabled, you also need to set the property `cds.multitenancy.sidecar.url` to the URL of the deployed MTX sidecar app. Now you can access the data of different tenants locally, if user information is set for the requests to your locally running server.
+
+You can locally authenticate at your app either through mock users or the UAA.
+
+The configuration of mock users is described in section [Security](./security). For a mock user, you can also set the `tenant` property. The value needs to be the subaccount ID, which can be found in SAP BTP Cockpit in the *Subaccount* details. You can then authenticate at your app using basic authentication. If you already secured your services, the browser asks you automatically for credentials. Otherwise, you can also set username and password explicitly, for example, in Postman.
+
+If you want to authenticate using the XSUAA, just copy the XSUAA service binding into the *default-env.json*. You then need to have a valid token for the tenant to authenticate. This can be obtained through client-credential-flow, for example, using Postman.
+
+::: warning *β Warning*
+Requests without user information fail!
+:::
+
+::: tip
+Currently you need to push the changes to Cloud Foundry, to update the database artifacts. If you're working on the data model, it's recommended to use a static SAP HANA binding.
+:::
+
+### Accessing Arbitrary Tenants
+
+You can override the tenant ID that is set in the current `RequestContext`. This enables accessing data of arbitrary tenants programmatically. This might be useful for example:
+
+- To access configuration data stored by means of a "technical" tenant while processing the request of a business tenant.
+- To access tenant data in asynchronously scheduled jobs, where no tenant information is present in the `RequestContext`, yet (for example, a startup task prefilling tables of tenants with certain data).
+
+You can use the [`TenantProviderService`](https://javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/mt/TenantProviderService.html) to get a list of available tenants. You can set a particular tenant and access it by running your code in [nested `RequestContext`](event-handlers/request-contexts#defining-requestcontext) as demonstrated by the following example:
+
+```java
+TenantProviderService tenantProvider = runtime.getServiceCatalog()
+ .getService(TenantProviderService.class, TenantProviderService.DEFAULT_NAME);
+List tenants = tenantProvider.readTenants();
+tenants.forEach(tenant -> {
+ runtime.requestContext().privilegedUser().modifyUser(user -> user.setTenant(tenant)).run(context -> {
+ // ... your code
+ });
+});
+```
+
+::: warning *β Warning*
+If an application overrides the default behavior of the CAP Java SDK this way, it's responsible of ensuring data privacy and isolation!
+:::
+
+### Data Source Pooling Configuration
+
+> Pretty much everything in this section depends on your modeling, the load, and also on the sizing (JVM/DB). As there's no one-size-fits-all recommendation, the mentioned configuration parameters are a good starting point.
+
+Data source pool configuration is a tradeoff between resources and latency:
+
+##### Pool per tenant - less latency, more resources
+
+The dedicated pools per tenant approach creates a dedicated connection pool for each tenant. In it's default
+configuration this strategy uses a static sizing approach: the number of configured connections (defaults to 10) is
+opened on startup of the application and kept open. This has the lowest possible latency and the highest resource
+consumption. The application will need a static number of connections per subscribed client. In case you need
+low latency but a bit less resource consumption you can [configure dynamic pool sizing](#configure-data-pools) for your tenants'
+connection pools. Then the application will need at least the minimum number of connections per subscribed clients. Depending
+on the concurrent load the number can increase per client until the configured maximum number of connections is reached.
+
+##### Pool per database - less resources, more latency { #combine-data-pools}
+
+The combined pool approach uses only one pool for all tenants holding a fixed number of connections. This approach, however, needs to switch
+connections to the correct schema and user of the tenant, before handing out the connection. This causes some additional latency compared to
+the pools per tenant, where a connection, once opened for a given schema and user, can be reused until it's retired. For the combined pool
+you can, as for the dedicated pools, decide between static sizing (the default) and [dynamic sizing](#configure-data-pools). For the latter the
+resource consumption can be even more reduced while adding a bit more latency because new database connections might be opened
+upon incoming requests.
+
+In order to activate the combined pool approach set the property `cds.multiTenancy.datasource.combinePools.enabled = true`.
+
+::: warning *β Warning*
+Since the pool is shared among all tenants, one tenant could eat up all available connections, either intentionally or by accident. Applications using combined pools need to take adequate measures to mitigate this risk, for example by introducing rate-limiting.
+:::
+
+#### Dynamic Data Source Pooling { #configure-data-pools}
+
+If not configured differently both, the dedicated pool and the combined pool approaches use static sizing strategies by default.
+
+The connections are kept open regardless of whether the application is currently serving requests for the given tenant. If you expect a low
+number of tenants, this shouldn't be an issue. With a large number of active tenants, this might lead to resource problems, for example, too
+many database connections or out-of-memory situations.
+
+Once you have an increased number of tenants, or run short of connections on the database side, you need to adjust the
+[configuration of the CDS datasource](./cqn-services/persistence-services#datasource-configuration) for HikariCP as described in the following section. We're using three parameters for the configuration:
+
+- `cds.dataSource..hikari.minimum-idle`
+- `cds.dataSource..hikari.maximum-pool-size`
+- `cds.dataSource..hikari.idle-timeout`
+
+Keep in mind that `` is the placeholder for the service manager instance bound to your CAP Java application.
+
+|Parameter |Description |
+|--------------------|-------------|
+|`minimum-idle` | The minimum number of connections kept in the pool after being considered as idle. This helps to adjust the usage of resources to the actual load of a given tenant at runtime. In order to save resources (Java heap and DB connections), this value should be kept rather small (for example `1`). |
+|`maximum-pool-size` | The maximum number of connections in the pool. Here, the value needs to be balanced. Counter-intuitively a bigger value doesn't necessarily lead to higher response/time or throughput. Closely monitor your application under load in order to find a good value. As a starting point you can just leave the default value `10`. |
+|`idle-timeout` | The time span after which a connection is considered as *idle*. It controls how fast the size of the pool is adjusted to the load of the application, until `minimum-idle` is reached. Keep in mind that opening a connection comes at a latency cost, too. Don't retire connections too soon. |
+
+See section [Multitenancy Configuration Properties](#mtx-properties) for more details.
+
+## Multitenancy Configuration Properties { #mtx-properties }
+
+A number of multitenancy settings can be configured through application configuration properties. See section [Application Configuration](./developing-applications/configuring#profiles-and-properties) for more details. All properties can be found in the [properties overview](./developing-applications/properties#cds-multiTenancy). The prefix for multitenancy-related settings is `cds.multitenancy`.
+
+
diff --git a/java/multitenancy.md b/java/multitenancy.md
new file mode 100644
index 000000000..9b77b5011
--- /dev/null
+++ b/java/multitenancy.md
@@ -0,0 +1,390 @@
+---
+synopsis: >
+ CAP applications can be run as software as a service (SaaS). That means, multiple customers (subscriber tenants) can use the application at the same time in an isolated manner.
+ Optionally, subscriber tenants may also extend their CDS models being served.
+status: released
+uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/9186ed9ab00842e1a31309ff1be38792.html
+---
+
+# Multitenancy { #multitenancy}
+
+{{ $frontmatter.synopsis }}
+
+## Setup Overview
+
+This chapter describes how CAP Java applications can deal with multiple business tenants.
+To add multitenancy flavour seamlessly, the CAP Java backend can be enriched with CAP multitenancy services as described in detail in general [Multitenancy](../guides/multitenancy/?impl-variant=java) guide.
+
+The overall setup is sketched in the following figure:
+
+
+
+The **MTX sidecar** services provide basic functionality such as:
+
+- Deploying or undeploying database containers during subscription or unsubscription of business tenants.
+- Managing CDS model [extensions](../guides/extensibility/customization#extending-saas-applications) for tenants.
+- Managing CDS models for [feature toggles](../guides/extensibility/feature-toggles#feature-toggles).
+
+There are different web adapters available in the Java backend to integrate with **platform services for tenant lifecycle** such as:
+
+- SAP BTP [SaaS Provisioning service](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/ed08c7dcb35d4082936c045e7d7b3ecd.html) for XSUAA tenants
+- SAP BTP Subscription Manager Service for IAS tenants.
+
+This chapter describes the APIs available in the **Java backend**, most notably the technical [DeploymentService](#custom-logic),
+which can be used to add custom handlers that influence or react on subscription or unsubscription events
+
+## React on Tenant Events { #custom-logic }
+
+CAP Java can automatically react on tenant lifecycle events sent by platform services such as [SaaS Provisioning service](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/ed08c7dcb35d4082936c045e7d7b3ecd.html).
+For these requests, CAP Java internally generates CAP events on the technical service [`DeploymentService`](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/mt/DeploymentService.html).
+
+[For a general introduction to CAP events, see Event Handlers.](../java/event-handlers/){.learn-more}
+
+Register event handlers for the following CAP events to add custom logic for requests sent by the platform service.
+Each event passes a special type of `EventContext` object to the event handler method and provides event-specific information:
+
+| Event Name | Event Context | Use Case |
+| -----------------| -------------------------------------------------------------------------------| --------------- |
+| `SUBSCRIBE` | [SubscribeEventContext](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/mt/SubscribeEventContext.html) | Add a tenant |
+| `UNSUBSCRIBE` | [UnsubscribeEventContext](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/mt/UnsubscribeEventContext.html) | Remove a tenant |
+| `DEPENDENCIES` | [DependenciesEventContext](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/mt/DependenciesEventContext.html) | Dependencies |
+
+You only need to register event handlers to override the default behavior.
+
+Default behaviors:
+
+- A new tenant-specific database container is created through the [Service Manager](https://help.sap.com/docs/SERVICEMANAGEMENT/09cc82baadc542a688176dce601398de/3a27b85a47fc4dff99184dd5bf181e14.html) during subscription.
+- The tenant-specific database container _is deleted_ during unsubscription.
+
+The following sections describe how to register to these events in more detail.
+
+### Subscribe Tenant
+
+Subscription events are generated when a new tenant is added.
+By default, subscription creates a new database container for a newly subscribed tenant. This happens during the `@On` phase of the `SUBSCRIBE` event.
+You can add additional `@On` handlers to perform additional subscription steps. Note that these `@On` handlers should not call `setCompleted()`, as the event processing is auto-completed.
+
+The following examples show how to register custom handlers for the `SUBSCRIBE` event:
+
+```java
+@Before
+public void beforeSubscription(SubscribeEventContext context) {
+ // Activities before tenant database container is created
+}
+
+@After
+public void afterSubscribe(SubscribeEventContext context) {
+ // For example, send notification, ...
+}
+```
+
+#### Defining a Database ID
+
+When you've registered exactly one SAP HANA instance in your SAP BTP space, a new tenant-specific database container is created automatically.
+However, if you've registered more than one SAP HANA instance in your SAP BTP space, you have to pass the target database ID for the new database container in a customer handler, as illustrated in the following example:
+
+```java
+@Before
+public void beforeSubscription(SubscribeEventContext context) {
+ context.getOptions().put("provisioningParameters",
+ Collections.singletonMap("database_id", ""));
+}
+```
+
+### Unsubscribe Tenant
+
+By default, the tenant-specific database container _is deleted_ during offboarding. This happens during the `@On` phase of the `UNSUBSCRIBE` event.
+You can add additional `@On` handlers to perform additional unsubscription steps. Note that these `@On` handlers should not call `setCompleted()`, as the event processing is auto-completed.
+
+The following example shows how to add custom logic for the `UNSUBSCRIBE` event:
+
+```java
+@Before
+public void beforeUnsubscribe(UnsubscribeEventContext context) {
+ // Activities before offboarding
+}
+
+@After
+public void afterUnsubscribe(UnsubscribeEventContext context) {
+ // Notify offboarding finished
+}
+```
+
+::: warning
+If you are accessing the tenant database container during unsubscription, you need to wrap the access into a dedicated ChangeSetContext or transaction. This ensures that the transaction to the tenant database container is committed, before the container is deleted.
+:::
+
+#### Skipping Deletion of Tenant Data
+
+By default, tenant-specific resources (for example, database containers) are deleted during removal. However, you can register a customer handler to change this behavior.
+This is required, for example, in case a tenant is subscribed to your application multiple times and only the last unsubscription should remove its resources.
+
+```java
+@Before
+public void beforeUnsubscribe(UnsubscribeEventContext context) {
+ if (keepResources(context.getTenant())) {
+ context.setCompleted(); // avoid @On handler phase
+ }
+}
+```
+
+### Define Dependent Services
+
+The event `DEPENDENCIES` fires when the platform service calls the [`getDependencies` callback](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/ff540477f5404e3da2a8ce23dcee602a.html).
+Hence, if your application consumes any reuse services provided by SAP, you must implement the `DEPENDENCIES` event to return the service dependencies of the application.
+The event must return a list of all of the dependent services' `xsappname` values.
+CAP automatically adds dependencies of services to the list, for which it provides dedicated integrations. This includes AuditLog and Event Mesh.
+::: tip
+The `xsappname` of an SAP reuse service that is bound to your application can be found as part of the `VCAP_SERVICES` JSON structure under the path `VCAP_SERVICES..credentials.xsappname`.
+:::
+
+The following example shows this in more detail:
+
+```java
+@Value("${vcap.services..credentials.xsappname}")
+private String xsappname;
+
+@On
+public void onDependencies(DependenciesEventContext context) {
+ List