Implementing Role-Based Access Control with Appwrite Database Permissions
Step-by-step guide to enforcing role-based read/write access on Appwrite database collections using native permission strings, with SDK test examples and recovery procedures.
05 Aug 2026, 20:07 UTC

Desired Outcome
A database collection where documents enforce read and write access based on user roles—such as admin, editor, and viewer—using Appwrite's native permission strings. After completing this guide, a user assigned the viewer role can read documents but not modify them, an editor can create and update documents, and an admin can delete any document in the collection.
Prerequisites
- Appwrite instance running version 1.5 or later (self-hosted or Cloud).
- An existing Appwrite project with a Database scope API key.
- At least one user account created via Appwrite Auth.
- A database collection created with Document Security enabled (the default for new collections).
If you are upgrading from a pre-1.4 version, note that the permission syntax changed; this guide assumes the v1.5+ format.
Procedure
1. Configure Collection-Level Permissions
Open the Appwrite Console, navigate to Databases → your database → your collection → Settings → Permissions. You will see a list of permission rules; each rule is a string following the pattern action("role:roleName").
Add the following rules for a typical three-role setup:
read("role:viewer")
read("role:editor")
read("role:admin")
create("role:editor")
create("role:admin")
update("role:editor")
update("role:admin")
delete("role:admin")Click Save. Changes take effect immediately for new requests; existing SDK sessions may need a token refresh.
2. Assign Roles to Users
Go to Auth → Users, select a user, and open the Roles tab. Add custom role strings exactly as used in the permission rules—viewer, editor, or admin. Roles are free-form strings; a typo (e.g., editer) will silently deny access because no rule matches.
Repeat for each test user. For this guide, create two users:
- user-viewer → role
viewer - user-editor → role
editor
3. Test with the Appwrite SDK
In your client or server code, initialize the SDK with the project endpoint and the user's JWT (obtained after login). The following Node.js example demonstrates the expected behavior:
const { Client, Databases, Account } = require('node-appwrite');
async function testPermissions(userEmail, userPassword) {
const client = new Client()
.setEndpoint('https://cloud.appwrite.io/v1') // or your self-hosted endpoint
.setProject('YOUR_PROJECT_ID');
const account = new Account(client);
const databases = new Databases(client);
// Login to get JWT
const session = await account.createEmailPasswordSession(userEmail, userPassword);
client.setJWT(session.jwt);
const dbId = 'main-db';
const collId = 'articles';
// Attempt create
try {
const doc = await databases.createDocument(dbId, collId, 'unique()', { title: 'Test' });
console.log('CREATE ok:', doc.$id);
} catch (e) { console.log('CREATE denied:', e.code); }
// Attempt read (use a known document ID)
try {
const doc = await databases.getDocument(dbId, collId, 'KNOWN_DOC_ID');
console.log('READ ok:', doc.title);
} catch (e) { console.log('READ denied:', e.code); }
// Attempt update
try {
await databases.updateDocument(dbId, collId, 'KNOWN_DOC_ID', { title: 'Updated' });
console.log('UPDATE ok');
} catch (e) { console.log('UPDATE denied:', e.code); }
// Attempt delete
try {
await databases.deleteDocument(dbId, collId, 'KNOWN_DOC_ID');
console.log('DELETE ok');
} catch (e) { console.log('DELETE denied:', e.code); }
}
// Run with each test user
// testPermissions('[contact removed]', 'password');
// testPermissions('[contact removed]', 'password');Where to run: Any environment with the node-appwrite SDK installed (Node.js 18+). Requires the user's email/password credentials or an existing JWT.
Required permissions: The API key used for server-side scripts must have Database scope; client-side SDKs rely on the user's JWT.
4. Verify in Console
Open Console → Logs → Database. Filter by permission denied to see rejected operations. Each entry shows the user ID, action, collection, and the rule that was evaluated.
Expected Checks
| User Role | Create | Read | Update | Delete |
|---|---|---|---|---|
| viewer | ❌ 401 | ✅ 200 | ❌ 401 | ❌ 401 |
| editor | ✅ 201 | ✅ 200 | ✅ 200 | ❌ 401 |
| admin | ✅ 201 | ✅ 200 | ✅ 200 | ✅ 204 |
Confirm the matrix matches your test runs. If a user with editor role receives a 401 on update, double-check the role string spelling in both the collection permissions and the user's assigned roles.
Recovery Options
Permission Misconfiguration
If rules behave unexpectedly, return to Collection → Settings → Permissions and click Reset to default. This restores the default read("any"), create("users"), update("users"), delete("users") rules, giving all authenticated users full access to their own documents.
Incorrect Role Assignment
In Auth → Users → target user → Roles, remove the erroneous role string and add the correct one. No server restart is required.
Collection Corruption or Schema Loss
If the collection becomes unusable, restore from an Appwrite backup (enabled under Console → System → Backups) or recreate the collection from a schema export (JSON) and re-apply the permission rules.
Limitations and Gotchas
- Document-level permissions override collection-level. If you set per-document permissions (via SDK
permissionsparameter on create/update), those take precedence. Avoid mixing unless intentional. - Custom roles are case-sensitive strings. Adopt a naming convention (e.g., lowercase, singular) and enforce it in code.
- Team-based permissions (
role:team:teamId) require the Teams feature enabled and the user added to the team; they are not covered in this basic RBAC setup. - Cached JWTs. After changing a user's roles, the user must log in again (or refresh their session) for the new roles to be evaluated.
Practical Verification Checklist
- Create two test users and assign
viewerandeditorroles respectively. - Run the SDK test script with each user's credentials.
- Confirm the console output matches the expected checks table.
- Open Console → Logs → Database and verify
permission deniedentries appear only for the actions that should be blocked. - Optional: Use the built-in API Playground (Console → Databases → your collection → API Playground) to simulate calls with different JWTs pasted into the Authorization header.
Once all checks pass, the RBAC layer is operational and ready for production use.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.