Securing Multi-Tenant Data with Appwrite Document-Level Permissions
Learn how to prevent data leakage in multi-tenant apps using Appwrite's document-level permissions and team-based access control.
17 Dec 2025, 12:38 UTC

The Multi-Tenancy Permission Gap
When building a multi-tenant application—such as a project management tool or a CRM—the biggest risk is "data leakage." This occurs when a user from Organization A accidentally (or intentionally) accesses a record belonging to Organization B. Many backend-as-a-service platforms force you to handle this logic in a middleware layer or through complex query filters that are easy to forget.
The solution in Appwrite is to shift security from the application logic to the data layer using Document-Level Permissions. Instead of relying on a global collection setting, you define exactly who can read or write a specific document at the moment of creation.
Understanding the Permission Model
Appwrite employs a "deny-by-default" security posture. If a document has no permissions assigned, no one—not even the creator—can access it via the Client SDK. Access is granted using permission strings that combine a Role (who) and an Action (what).
- Roles: These can be generic (
anyfor public,usersfor any authenticated user), specific (user:[USER_ID]), or group-based (team:[TEAM_ID]). - Actions: These correspond to CRUD operations:
read,create,update, anddelete.
For multi-tenant apps, the team:[TEAM_ID] role is the most scalable choice. It allows you to group users into a tenant and grant that entire group access to a shared set of documents without listing every individual user ID in the permission string.
Implementation: Team-Based Access Control
To implement this, you first create a Team for your tenant. When creating a document, you assign permissions to that Team ID. This ensures that only members of that specific team can interact with the data.
Worked Example: Creating a Tenant-Locked Document
In this scenario, we assume you are using the Appwrite Web SDK and have already created a team with the ID tenant_123. The following code creates a document that only members of tenant_123 can read or update.
// Run this in your frontend application logic
import { Client, Databases } from 'appwrite';
const client = new Client()
.setEndpoint('https://cloud.appwrite.io/v1')
.setProject('[PROJECT_ID]');
const databases = new Databases(client);
async function createTenantDocument(data) {
try {
const response = await databases.createDocument(
'[DATABASE_ID]',
'[COLLECTION_ID]',
'unique()', // Generate a unique ID
data,
[
'read:team:tenant_123',
'update:team:tenant_123',
'delete:team:tenant_123'
]
);
console.log('Document secured for tenant:', response.$id);
} catch (error) {
console.error('Permission error:', error.message);
}
}
Verification and Diagnostics
To verify this configuration, perform the following checks:
- Authorized Access: Log in as a user who is a member of
tenant_123. Attempt to fetch the document. You should receive a200 OKresponse. - Unauthorized Access: Log in as a user not in
tenant_123. Attempt to fetch the same document. Appwrite should return a403 Forbiddenerror. - Public Access: Log out completely. Attempt to fetch the document. You should receive a
401 Unauthorizedor403 Forbiddenresponse.
Trade-offs and Limitations
While document-level security is powerful, it introduces a management trade-off. As your dataset grows to millions of documents, managing individual permission strings can become complex. If you need to change a tenant's access level (e.g., upgrading them from "Read-Only" to "Editor"), you cannot update a single global switch; you must update the permissions on every document that tenant owns.
To mitigate this, use the Server SDK for administrative tasks. The Server SDK uses an API Key with administrative privileges, which bypasses all client-side permission checks. This allows you to run batch updates on permissions without needing to be a member of the target team.
Summary Checklist
When configuring your multi-tenant security, follow these rules:
- Avoid using
anyorguestsin tenant-specific collections. - Prefer
team:[ID]over listing multipleuser:[ID]strings to keep permission arrays small. - Use the Server SDK for bulk permission migrations or administrative overrides.
- Always test the
403 Forbiddenresponse with a non-member account before deploying to production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.