---
source: https://carbone.io/documentation/developer/embedding/multi-tenancy.html
title: "Multi-Tenancy"
description: "How to enable multi-tenancy for On-Premise installation or embedded studio"
generated_at: "2026-07-13"
---

# Multi-Tenancy

How to enable multi-tenancy for On-Premise installation or embedded studio

## Overview

Whether you need multi-tenant separation in Carbone depends on [how you execute Carbone On-Premise](/documentation/developer/on-premise-installation/introduction.md#execution-modes). Below is a non-exhaustive list of use cases indicating when multi-tenancy separation may be required:

| Execution Mode | Template source | Tenant boundary | Carbone separation needed? |
| --- | --- | --- | --- |
| Stateless (Ephemeral templates) | The template is sent **with every render request** (e.g., base64) via `POST /render/template`. **Nothing is stored** by the Carbone backend. | **Your application** must enforce tenant separation (auth, access control, routing). | **Not required in Carbone**. Carbone acts as a stateless generator. |
| Hybrid (Template storage only) | Templates are stored as **files** (by Carbone or by your app), optionally on **S3/other storage**. Carbone does not manage tenant metadata. | **Your application** maintains the association **`template ↔ tenantId`** and enforces tenant access control. | **Usually not required in Carbone**. Optional separation can be done at the storage layer (see below). |
| Stateful (Template versioning) | Carbone enables its **SQLite database** to support features like **template listing, search, versioning, deployment**. Carbone manages template storage (disk and/or S3) and stores template metadata in SQLite (deployed version, name, comments, tags, category…). | **Carbone should enforce tenant separation** because Carbone is now the system of record for template metadata and discovery features. | **Recommended** to separate by tenant in Carbone (e.g., to list/search templates per tenant safely). |

## Set the tenantId

To enable tenant isolation, set `req.tenantId` in the [HTTP request hook middleware plugin](/documentation/developer/on-premise-installation/plugins.md#http-request-hook).

This is also the place where you can authenticate your users by reading a cookie or relying on an external authentication service such as Google OpenID, Microsoft Azure AD, Okta, Auth0, OneLogin, Keycloak, Ory, and others.

```js
// HTTP middleware called before all routes
function beforeMiddleware(req, res, next) {
  // Provide the tenant id using your own logic:
  // header from your application, a token, external auth service, etc.
  req.tenantId = req.headers['custom-tenant-id'];
  return next();
}
```

## Isolation behavior

-   **Template storage:**
    -   without storage plugins: template files use the pattern `tenantId-versionId` automatically.
    -   with [custom storage plugins](/documentation/developer/on-premise-installation/plugins.md): you must enforce tenant separation by reusing `req.tenantId` previously set (naming convention, per-tenant prefix, bucket separation, etc.) in `writeTemplate`, `readTemplate`, or `deleteTemplate` functions.
-   **SQLite Database (optional):** rows are automatically isolated by `tenantId`.
-   **API behavior:** all endpoints become tenant-aware (e.g. `GET /template` returns only templates for the current tenant).

## Constraints

-   `req.tenantId` must be a non-negative signed 64-bit integer.
-   Because JavaScript integers are only safe up to 53 bits, you can pass `tenantId` as a string to support the full 64-bit range: from `1` to `9223372036854775807`
-   If the value is invalid, Carbone returns an error.

If `tenantId` is undefined or null, Carbone automatically disables tenant isolation. In this case, all data in the SQLite database will be stored with `tenantId = 0` (see [Security](#security) below).

## Security

When `req.tenantId` is not set (i.e., it is undefined or null), Carbone will NOT separate data between tenants. This can be a security risk if you expect data isolation.

To force tenant isolation for all API requests and block requests where `req.tenantId` is missing (for example, due to a client mistake or misconfigured authentication), set the security level configuration:

-   Set the environment variable `CARBONE_SECURITY_LEVEL=2`.
-   Or, in your config file, set `securityLevel: 2`

> ⚠️ **Warning:** `2` is a bitfield (`0b00000010`) that specifically enables tenantId isolation enforcement.

This enables an additional security check:  
If a request does not include a valid `tenantId`, Carbone will reject it instead of disabling isolation.

## Related topics

- [Studio Web Component](/documentation/developer/embedding/studio-web-component.md)
- [Embedding in Node.js](/documentation/developer/embedding/embedding-in-node.md)
