Appwrite Multi‑Tenant Isolation Using Team‑Based Collection Permissions
How to enforce multi‑tenant isolation in Appwrite using team‑based collection permissions and JWT‑verified team membership.
26 Aug 2026, 10:11 UTC

The Problem: Preventing Cross‑Tenant Data Leakage
In a multi‑tenant SaaS application the most serious risk is "tenant bleeding" – a user from one tenant gaining read or write access to another tenant’s data. Relying solely on application‑level filters is fragile; a missed check or a mistaken query can expose data.
Moving the authorization boundary into the data store provides a stronger guarantee: the database rejects unauthorized requests before they reach your service logic.
Smallest Suitable Design: One Project, Team‑Scoped Collections
Appwrite’s permission model evaluates requests against static identifiers: any, role:, team:, or user:. Arbitrary expressions like tenantId == request.tenantId are not supported. Therefore the trust boundary must be built using identifiers that Appwrite can evaluate directly.
The simplest workable architecture is:
- A single Appwrite project containing one database.
- For each tenant, create a dedicated Appwrite team (e.g.,
team:tenant_alpha). - When a user signs up, your auth service adds them to the appropriate team.
- Create a separate collection for each tenant and set its permissions to
team:for the desired operations (read, write, create, delete).
Because the JWT issued by Appwrite’s auth service includes the user’s team memberships, Appwrite can verify that the requestor belongs to the team named in the collection permission.
Example: Creating a Tenant‑Scoped Collection via CLI
# 1. Create a team for the tenant (run once per tenant)
appwrite create team --name "Tenant Alpha" --tenantId "tenant_alpha" --output team_alpha_id
# 2. Add a user to the team (run after user registration)
appwrite update teamMembership --teamId $team_alpha_id --userId $user_id
# 3. Create a collection that only members of the team can read/write
appwrite create collection --databaseId $db_id --name "alpha_data" \
--read "team:$team_alpha_id" \
--write "team:$team_alpha_id" \
--create "team:$team_alpha_id" \
--delete "team:$team_alpha_id"
The permission strings above are the exact format Appwrite expects. No custom JWT claims are required; the team ID is the trust anchor.
Trust and Data Boundaries
The trust boundary is the moment Appwrite verifies the JWT signature issued by its auth service. If the signature is valid, the payload—including the list of team IDs the user belongs to—is treated as immutable truth. When a request targets a collection, Appwrite checks whether any of the user’s team IDs match the permission string on that collection. If there is a match, the operation proceeds; otherwise Appwrite returns 403 Forbidden.
Critical warning: Never expose your Appwrite Project API key in client‑side code. The API key bypasses all permission checks and grants administrative access. Client requests must use a user session (JWT) or a scoped API key created with limited permissions.
Operational Checks and Monitoring
To verify that isolation remains effective in production, monitor the following signals:
| Metric / Log | Threshold / Check | Suggested Action |
|---|---|---|
permission_denied logs |
Any sustained increase > 0.5% of requests | Inspect for mis‑assigned team memberships or tampered JWTs. |
db_request_latency |
Median > 200 ms for CRUD | Review collection size, indexes, or consider sharding. |
| JWT signing key age (if using custom JWT strategy) | > 30 days | Rotate the secret and update the kid header to force re‑verification. |
| Team count per project | Approaching the documented limit for teams/collections | Plan migration to a shared‑collection model with document‑level permissions. |
Failure Modes
- Malformed or expired JWT: Results in
401 Unauthorized. The client must obtain a fresh session. - Incorrect team assignment: If a user is not a member of the team named in the collection permission, Appwrite returns
403 Forbidden. This appears as a permission denial in logs. - Collection‑level permission mis‑configuration: Typing an invalid team ID (e.g.,
team:unknown) causes the collection creation/update to fail with a validation error. - Resource limits: Exceeding the maximum number of collections (or teams) per project triggers a quota error when attempting to create additional collections.
- Network partition to Appwrite API: The SDK receives retry‑able
502 Bad Gatewayor503 Service Unavailableresponses; implement exponential back‑off.
When the Design Must Change
- Need for cross‑tenant queries: If a single request must read data from multiple tenants (e.g., analytics dashboard), the collection‑per‑team model cannot satisfy it because Appwrite does not support querying across collections in one operation. The alternative is to move to a single collection with a
tenantIdattribute and enforce access via document‑level permissions (read/write rules that compare the document’stenantIdclaim to the user’s team). - Collection or team quota exceeded: When the number of tenants grows beyond the project’s limit for collections/teams, you must consolidate tenants into shared collections and rely on document‑level security.
- Complex role hierarchies: Appwrite permissions only test equality against a team ID. If you require nested roles (e.g., “regional manager” can see all “store manager” data), flatten the hierarchy into team memberships at token‑issuance time, or introduce a proxy layer that maps roles to appropriate team IDs before calling Appwrite.
Verification Step (Illustrative)
To confirm that the boundary works as intended, perform the following checks using the Appwrite CLI or an SDK:
- Create a team
team:tenant_alphaand a collectionalpha_datawith permissionteam:team_alpha_idfor read and write. - Create two users:
user_aadded toteam:tenant_alpha, anduser_bwith no team membership. - Obtain a JWT session for each user.
- Attempt to read
alpha_datawithuser_a’s JWT → expect200 OK. - Attempt the same read with
user_b’s JWT → expect403 Forbidden.
Any deviation indicates a mis‑configuration in team assignment or permission strings and should be investigated before promoting the setup to production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.