Permission Routes
Table of Contents
Get Role Permissions
Retrieve all permissions for a specific role.
- URL:
/permissions/:roleId - Method:
GET - Auth required: Yes (Admin role)
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| roleId | string | Yes | ID of the role |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| tenant_Id | string | No | Filter by tenant ID (multi-tenant mode only) |
| collection | string | No | Filter permissions by collection name |
Success Response
- Code: 200 OK
- Content:
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"role_Id": "4a7d1ed4-8d7f-44b3-8f5e-7e5f2d8a7d3c",
"collection": "products",
"action": "read",
"fields": ["*"],
"conditions": {},
"createdAt": "2023-06-01T12:00:00Z",
"updatedAt": "2023-06-01T12:00:00Z"
},
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"role_Id": "4a7d1ed4-8d7f-44b3-8f5e-7e5f2d8a7d3c",
"collection": "products",
"action": "create",
"fields": ["name", "description", "price", "category_id"],
"conditions": {},
"createdAt": "2023-06-01T12:00:00Z",
"updatedAt": "2023-06-01T12:00:00Z"
}
]
}Error Responses
-
Code: 401 Unauthorized
- Content:
{ "error": { "message": "Authentication required" } }
- Content:
-
Code: 403 Forbidden
- Content:
{ "error": { "message": "Insufficient permissions" } }
- Content:
-
Code: 404 Not Found
- Content:
{ "error": { "message": "Role not found" } }
- Content:
-
Code: 500 Internal Server Error
- Content:
{ "error": { "message": "Error retrieving permissions" } }
- Content:
Create Permission
Create a new permission for a role.
- URL:
/permissions - Method:
POST - Auth required: Yes (Admin role)
- Content-Type:
application/json
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| role_Id | string | Yes | ID of the role |
| collection | string | Yes | Collection name for this permission |
| action | string | Yes | Permission action (read, create, update, delete) |
| fields | array/string | No | Array of field names or "*" for all fields |
| conditions | object | No | Row filter (USING) for read/update/delete — rejected on create grants |
| checkConditions | object | No | WITH CHECK: what a written row must satisfy on create/update (403 + rollback otherwise) |
| tenant_Id | string | No | Tenant ID (required in multi-tenant mode) |
Example:
{
"role_Id": "4a7d1ed4-8d7f-44b3-8f5e-7e5f2d8a7d3c",
"collection": "products",
"action": "read",
"fields": ["*"],
"conditions": {
"user_id": { "$CURRENT_USER": "id" }
},
"tenant_Id": "8a7d1ed4-8d7f-44b3-8f5e-7e5f2d8a7d3c"
}Success Response
- Code: 201 Created
- Content:
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"role_Id": "4a7d1ed4-8d7f-44b3-8f5e-7e5f2d8a7d3c",
"collection": "products",
"action": "read",
"fields": ["*"],
"conditions": {
"user_id": { "$CURRENT_USER": "id" }
},
"tenant_Id": "8a7d1ed4-8d7f-44b3-8f5e-7e5f2d8a7d3c",
"createdAt": "2023-06-01T12:00:00Z",
"updatedAt": "2023-06-01T12:00:00Z"
}
}Error Responses
-
Code: 400 Bad Request
- Content:
{ "error": { "message": "Invalid permission data" } }
- Content:
-
Code: 401 Unauthorized
- Content:
{ "error": { "message": "Authentication required" } }
- Content:
-
Code: 403 Forbidden
- Content:
{ "error": { "message": "Insufficient permissions" } }
- Content:
-
Code: 500 Internal Server Error
- Content:
{ "error": { "message": "Error creating permission" } }
- Content:
Update Permission
Update an existing permission.
- URL:
/permissions/:id - Method:
PATCH - Auth required: Yes (Admin role)
- Content-Type:
application/json
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Permission ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| action | string | No | Permission action (read, create, update, delete) |
| fields | array/string | No | Array of field names or "*" for all fields |
| conditions | object | No | Conditions that limit the scope of the permission |
Example:
{
"fields": ["id", "name", "price", "description"],
"conditions": {
"status": "active"
}
}Success Response
- Code: 200 OK
- Content:
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"role_Id": "4a7d1ed4-8d7f-44b3-8f5e-7e5f2d8a7d3c",
"collection": "products",
"action": "read",
"fields": ["id", "name", "price", "description"],
"conditions": {
"status": "active"
},
"tenant_Id": "8a7d1ed4-8d7f-44b3-8f5e-7e5f2d8a7d3c",
"createdAt": "2023-06-01T12:00:00Z",
"updatedAt": "2023-06-01T13:30:00Z"
}
}Error Responses
-
Code: 400 Bad Request
- Content:
{ "error": { "message": "Invalid permission data" } }
- Content:
-
Code: 401 Unauthorized
- Content:
{ "error": { "message": "Authentication required" } }
- Content:
-
Code: 403 Forbidden
- Content:
{ "error": { "message": "Insufficient permissions" } }
- Content:
-
Code: 404 Not Found
- Content:
{ "error": { "message": "Permission not found" } }
- Content:
-
Code: 500 Internal Server Error
- Content:
{ "error": { "message": "Error updating permission" } }
- Content:
Delete Permission
Delete a permission.
- URL:
/permissions/:id - Method:
DELETE - Auth required: Yes (Admin role)
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Permission ID |
Success Response
- Code: 200 OK
- Content:
{
"message": "Permission deleted successfully"
}Error Responses
-
Code: 401 Unauthorized
- Content:
{ "error": { "message": "Authentication required" } }
- Content:
-
Code: 403 Forbidden
- Content:
{ "error": { "message": "Insufficient permissions" } }
- Content:
-
Code: 404 Not Found
- Content:
{ "error": { "message": "Permission not found" } }
- Content:
-
Code: 500 Internal Server Error
- Content:
{ "error": { "message": "Error deleting permission" } }
- Content:
Bulk Update Permissions
Update multiple permissions for a role in a single operation.
- URL:
/permissions/bulk/:roleId - Method:
POST - Auth required: Yes (Admin role)
- Content-Type:
application/json
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| roleId | string | Yes | ID of the role |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| tenant_Id | string | No | Tenant ID (required in multi-tenant mode) |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| permissions | array | Yes | Array of permission objects |
Example:
{
"permissions": [
{
"collection": "products",
"action": "read",
"fields": ["*"],
"conditions": {}
},
{
"collection": "products",
"action": "create",
"fields": ["name", "description", "price", "category_id"],
"conditions": {}
},
{
"collection": "products",
"action": "update",
"fields": ["name", "description", "price"],
"conditions": {
"user_id": { "$CURRENT_USER": "id" }
}
},
{
"collection": "products",
"action": "delete",
"fields": [],
"conditions": {
"user_id": { "$CURRENT_USER": "id" }
}
}
]
}Success Response
- Code: 200 OK
- Content:
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"role_Id": "4a7d1ed4-8d7f-44b3-8f5e-7e5f2d8a7d3c",
"collection": "products",
"action": "read",
"fields": ["*"],
"conditions": {},
"tenant_Id": "8a7d1ed4-8d7f-44b3-8f5e-7e5f2d8a7d3c",
"createdAt": "2023-06-01T12:00:00Z",
"updatedAt": "2023-06-01T12:00:00Z"
}
// ... other permissions
],
"message": "Permissions updated successfully"
}Error Responses
-
Code: 400 Bad Request
- Content:
{ "error": { "message": "Invalid permissions data" } }
- Content:
-
Code: 401 Unauthorized
- Content:
{ "error": { "message": "Authentication required" } }
- Content:
-
Code: 403 Forbidden
- Content:
{ "error": { "message": "Insufficient permissions" } }
- Content:
-
Code: 404 Not Found
- Content:
{ "error": { "message": "Role not found" } }
- Content:
-
Code: 500 Internal Server Error
- Content:
{ "error": { "message": "Error updating permissions" } }
- Content:
Permission Structure
Actions
The following actions can be assigned as permissions:
read: Allows reading items from a collectioncreate: Allows creating new items in a collectionupdate: Allows updating existing items in a collectiondelete: Allows deleting items from a collection
Fields
The fields property controls which fields the role can access. BAASIX supports powerful wildcard patterns for field-level permissions.
Wildcard Patterns
| Pattern | Description | Example |
|---|---|---|
* | The collection's own/direct fields only — does not include relations | ["*"] |
relation.* | All fields of a specific relation (one level deep) | ["*", "author.*"] |
relation.*.* | A relation's fields plus its own relations (one level deeper) | ["*", "posts.*.*"] |
Pattern Matching Rules
- Each
*segment matches exactly one level.*= own fields;author.*= the author relation's direct fields (allowsauthor.namebut notauthor.role.name). - To go deeper, add another
.*per level:author.*.*allowsauthor.role.name(the author's relations, one level further). There is no**syntax — use repeated.\*segments. - Critical: a bare
*grants only the collection's own columns. Any relation you want returned must be named explicitly (e.g."author.*"). A relation not listed infieldsis omitted from the response entirely, even ifrelConditionstargets it.
Examples
-
All fields access:
{ "fields": ["*"] }Allows access to the collection's own/direct fields only. This does not include any relations — to return a relation, add it explicitly (e.g.
["*", "author.*"]). -
Specific fields only:
{ "fields": ["id", "name", "email", "status"] }Allows access only to the specified fields.
-
All fields plus one relation:
{ "fields": ["*", "author.*"] }Allows access to all direct fields plus all fields of the
authorrelation. -
All fields plus nested relations (one level):
{ "fields": ["*", "author.*", "category.*"] }Allows access to all direct fields plus all fields of
authorandcategoryrelations. -
Full relation tree access:
{ "fields": ["*", "posts.*.*"] }Allows access to all direct fields plus the entire
postsrelation tree (posts, posts.comments, posts.comments.author, etc.) -
Mixed specific and wildcard:
{ "fields": ["id", "title", "author.name", "author.email", "category.*"] }Allows access to specific fields from the main collection, specific fields from
author, and all fields fromcategory. -
Restricted sensitive data:
{ "fields": ["id", "name", "email", "author.id", "author.name"] }Allows access to specific fields only - useful for hiding sensitive data like passwords, internal IDs, etc.
Conditions
The conditions property limits the scope of the permission using filtering expressions. This applies to the main collection being queried — it decides which existing records of this collection the role can access (RLS "USING" semantics). It applies to read, update, and delete grants.
conditionsis not valid on create grants — there are no existing rows to filter, so the API rejects it with a 400. To scope what a role may create, usecheckConditions.
conditionsvsrelConditions:conditionsfilters the main records (which rows of this collection appear).relConditionsfilters the related rows returned inside array relations (which child rows appear in the response). They are not interchangeable — "which rows of this collection?" →conditions; "which related rows in the response?" →relConditions.
{
"status": "active" // Only items where status is "active"
}{
"department_id": { "$CURRENT_USER": "department.id" } // Only items in user's department
}{
"created_by": { "$CURRENT_USER": "id" } // Only items created by the current user
}Check Conditions (checkConditions)
The checkConditions property is the WITH CHECK side of row-level security (mirroring PostgreSQL RLS): it validates what a written row may become. After a create or update — once hooks, defaults, and relations have been applied, but before commit — the row is re-read with checkConditions applied as a filter. If it doesn't match, the request fails with 403 and the whole write rolls back (bulk create/update batches are atomic: one violating item rolls back the entire batch).
checkConditions: null (the default) means no post-write check — existing grants are unaffected.
{
"collection": "tasks",
"action": "create",
"fields": ["*"],
"checkConditions": { "owner_Id": { "eq": "$CURRENT_USER" } }
}Users with this grant can only create tasks assigned to themselves — creating a task for someone else fails and rolls back.
On update, conditions and checkConditions compose — conditions decides which rows may be edited, checkConditions decides what they may become:
{
"collection": "documents",
"action": "update",
"fields": ["*"],
"conditions": { "owner_Id": { "eq": "$CURRENT_USER" }, "status": { "eq": "draft" } },
"checkConditions": { "owner_Id": { "eq": "$CURRENT_USER" }, "status": { "in": ["draft", "submitted"] } }
}This reads as: "you may edit your own drafts; the result must still belong to you and be draft or submitted." Submitting a draft works; archiving it or reassigning it to another user fails. Note that naively re-checking conditions after the update would forbid the legitimate draft → submitted transition — that is exactly why the two properties are separate.
checkConditions supports everything conditions does: all filter operators, dynamic variables ($CURRENT_USER, $CURRENT_USERROLE.*, …), and $path$ relation keys for cross-record rules:
{
"collection": "leave_balance",
"action": "create",
"fields": ["*"],
"checkConditions": { "$staff_profile.orguser_Id$": { "eq": "$CURRENT_USER" } }
}Users can only create a leave balance attached to their own staff profile — the check joins through the just-created row's relation.
Additional notes:
- Rejected writes fire no after-hooks and write no audit-log rows (they never commit).
- Administrators and system-context operations are exempt.
- When a permission uses ACL entries,
checkConditionsis still read from the permission row itself — ACL entries don't carry it.
Relational Conditions (relConditions)
The relConditions property is a deep filter for filtering within relations that return arrays. When a permission includes O2M (One-to-Many) or M2M (Many-to-Many) relations, those return as arrays. The relConditions filters what items appear in those arrays. It is keyed by relation name and does not restrict the main records.
Note: The property is always relConditions (plural).
Prerequisite: relConditions only takes effect on relations that are also granted in fields (e.g. "reviews.*"). If a relation isn't listed in fields, it's omitted from the response entirely and relConditions for it has no effect.
When to Use relConditions
| Relation Type | Returns | Use relConditions? |
|---|---|---|
| BelongsTo (N:1) | Single object | No |
| HasOne (1:1) | Single object | No |
| HasMany (O2M) | Array | ✅ Yes |
| BelongsToMany (M2M) | Array | ✅ Yes |
Examples
-
Filter comments array within posts:
{ "collection": "posts", "action": "read", "fields": ["*", "comments.*"], "relConditions": { "comments": { "isApproved": { "eq": true } } } }Users can read posts, but each post's
commentsarray only contains approved comments. -
Filter roles array for users (M2M):
{ "collection": "users", "action": "read", "fields": ["*", "roles.*"], "relConditions": { "roles": { "status": { "eq": "active" } } } }Each user's
rolesarray only contains active roles. -
Dynamic variables in relConditions:
{ "collection": "projects", "action": "read", "fields": ["*", "tasks.*"], "relConditions": { "tasks": { "assignedTo": { "eq": "$CURRENT_USER" } } } }Users can see all projects, but each project's
tasksarray only contains tasks assigned to them. -
Nested relConditions (array within array):
{ "collection": "organizations", "action": "read", "fields": ["*", "departments.*", "departments.employees.*"], "relConditions": { "departments": { "status": { "eq": "active" }, "employees": { "status": { "eq": "active" } } } } }Each organization's
departmentsarray contains only active departments, and each department'semployeesarray contains only active employees.
Combining conditions and relConditions
You can use both conditions (to filter main records) and relConditions (to filter related records) together:
{
"collection": "posts",
"action": "read",
"fields": ["*", "comments.*"],
"conditions": {
"status": "published"
},
"relConditions": {
"comments": {
"isApproved": { "eq": true }
}
}
}This permission allows reading only published posts, and for those posts, only approved comments are loaded.
Dynamic Variables
Permissions support dynamic variables that reference properties of the current user, role, tenant, or settings:
$CURRENT_USER: The ID of the authenticated user$CURRENT_USER.field: Any field of the user (e.g.,$CURRENT_USER.email,$CURRENT_USER.department)$CURRENT_ROLE: The ID of the current role$CURRENT_ROLE.field: Any field of the role$CURRENT_USERROLE: The ID of the active role-assignment (baasix_UserRole) row$CURRENT_USERROLE.field: Any field of the assignment row, including custom columns added tobaasix_UserRole(e.g.,$CURRENT_USERROLE.team_id) or relational paths (e.g.,$CURRENT_USERROLE.team.name)$CURRENT_TENANT: The ID of the current tenant (multi-tenant)$CURRENT_TENANT.field: Any field of the tenant (e.g.,$CURRENT_TENANT.name)$CURRENT_SETTINGS.field: Any settings field, tenant-aware (e.g.,$CURRENT_SETTINGS.currency,$CURRENT_SETTINGS.language)$NOW: Current timestamp$NOW+DAYS_7,$NOW-HOURS_2, etc.: Relative date calculations
Scoping Access by Role Assignment
$CURRENT_USERROLE is useful when a user can belong to multiple teams (or other groupings) through separate role assignments, and permissions should scope to whichever assignment is currently active.
Say baasix_UserRole has a custom team_id column (added via a relationship to a teams collection). A user who belongs to two teams has two baasix_UserRole rows, one per team. A permission condition can restrict reads to the active team:
{
"role_Id": "member-role-uuid",
"collection": "tasks",
"action": "read",
"fields": ["*"],
"conditions": {
"team_id": { "eq": "$CURRENT_USERROLE.team_id" }
}
}defaultValues can stamp the same column on create, so new records are automatically scoped to the active team:
{
"defaultValues": {
"team_id": "$CURRENT_USERROLE.team_id"
}
}The user switches their active assignment with POST /auth/switch-tenant, passing userRole_Id instead of tenant_Id:
POST /auth/switch-tenant
Authorization: Bearer <token>
Content-Type: application/json
{
"userRole_Id": "the-other-assignment-row-id"
}Once the new assignment is pinned, $CURRENT_USERROLE.team_id resolves to the other team's ID — so both the read condition and the create defaultValues flip to the newly active team without any other change. See Multi-Tenant Guide for details on userRole_Id switching.
Related Documentation
Data Access
- Item Routes - Permission-controlled data access
- Schema Routes - Schema-level permissions
- Schema Reference Guide - Field types and data models
Query System
- Complete Filter Reference - Dynamic variables in permission conditions ($CURRENT_USER, $CURRENT_USERROLE, $CURRENT_TENANT, $CURRENT_SETTINGS)
- Advanced Query Guide - Permission-filtered queries
Authentication
- Authentication Routes - User authentication and roles
Multi-tenant
- Multi-tenant Guide - Tenant-specific permissions and isolation
Automation
- Hooks System - Permission-related hooks
Reference
- API Routes Reference - Complete endpoint listing
- Integration Guide - Permission-aware client implementation