Skip to main content
SnackBase uses a powerful expression language for defining granular access control rules.

Overview

Permissions are defined per Role and Collection. Each permission rule consists of:
  1. Operation: create, read, update, delete
  2. Rule Expression: A logic string that evaluates to true (allow) or false (deny)
  3. Allowed Fields: A list of fields (or *) that are accessible

Permission Resolution Order

SnackBase resolves permissions in the following order:
  1. Role-Specific Rules: Rules for the user’s role on the specific collection
  2. Wildcard Collection Rules: Rules for the * collection apply if no specific collection rule matches
  3. Deny by Default: If no rule matches, access is denied
If multiple permissions match (e.g., role + wildcard), they are combined with OR logic.

Superadmin Bypass

Users with account_id == "00000000-0000-0000-0000-000000000000" (superadmins) bypass ALL permission checks.

Rule Syntax

Variables

VariableDescriptionFields
userThe currently authenticated userid, email, role, account_id, groups
recordThe record being accessedAll record fields (e.g., id, owner_id, status)
accountThe current account contextid

Operators

CategoryOperatorDescriptionExample
Comparison==Equal touser.id == record.owner_id
!=Not equal torecord.status != "archived"
< >Less/Greater thanrecord.score > 10
<= >=Less/Greater or equalrecord.amount >= 100
inMembership check"admin" in user.groups
LogicalandLogical ANDuser.isActive and record.public
orLogical ORuser.role == "admin" or record.public
notLogical NOTnot record.is_locked

Functions

FunctionDescriptionExample
contains(list, item)Checks if list contains itemcontains(user.groups, "manager")
starts_with(str, prefix)Checks string prefixstarts_with(record.sku, "PROD-")
ends_with(str, suffix)Checks string suffixends_with(user.email, "@company.com")

Literals

  • Strings: "text" or 'text'
  • Numbers: 123, 45.67
  • Booleans: true, false
  • Null/None: null
  • Lists: ['a', 'b', 'c']

Built-in Macros

Built-in macros are predefined functions that simplify common permission patterns.

@has_group(group_name)

Check if the user belongs to a specific group.
# Allow access to managers only
@has_group("managers")

# Alternative syntax
"managers" in user.groups

@has_role(role_name)

Check if the user has a specific role.
# Allow access to admins only
@has_role("admin")

# Alternative syntax
user.role == "admin"

@owns_record() / @is_creator()

Check if the user owns the record.
# Allow users to manage their own records
@owns_record()

# Alternative syntax
user.id == record.owner_id

@in_time_range(start_hour, end_hour)

Check if the current time is within a specific hour range.
# Allow access only during business hours (9 AM - 5 PM)
@in_time_range(9, 17)

@has_permission(operation, collection)

Check if the user has a specific permission on a collection.
# Allow users who can delete posts to also manage comments
@has_permission("delete", "posts")

SQL Macros

SQL macros allow you to create custom permission logic using SQL queries.

Creating SQL Macros

POST /api/v1/macros
{
  "name": "is_department_head",
  "description": "Check if user is department head",
  "parameters": ["department_id"],
  "sql_query": "SELECT EXISTS(SELECT 1 FROM department_members WHERE user_id = :user_id AND department_id = :department_id AND role = 'head')"
}

Using SQL Macros

@is_department_header("dept_123")

SQL Macro Features

  • Parameter Binding: Safe parameter binding to prevent SQL injection
  • 5-Second Timeout: Queries are automatically terminated after 5 seconds
  • Error Handling: Query errors return false (deny) for security
  • Result Caching: Results are cached per-request for performance

System Fields

System fields are automatically managed by SnackBase and cannot be written via the API.
FieldDescription
idAuto-generated record identifier
account_idAccount/tenant identifier (auto-set)
created_atRecord creation timestamp (auto-set)
updated_atRecord update timestamp (auto-set)
created_byUser ID who created the record (auto-set)
updated_byUser ID who last updated the record
Any attempt to include system fields in a request body will result in a 422 error.

Field-Level Access Control

Each permission rule can specify allowed fields:
{
  "operation": "read",
  "rule": "true",
  "fields": ["id", "title", "status"]  // Only these fields
}
Use * to allow all fields (except system fields):
{
  "operation": "read",
  "rule": "true",
  "fields": "*"  // All non-system fields
}

Field Filtering Behavior

ContextSystem FieldsBehavior
RequestExcludedOnly allowed fields can be written
ResponseAlways IncludedAllowed fields + system fields are returned

Permission Caching

SnackBase uses a high-performance permission cache:
  • Default TTL: 5 minutes (300 seconds)
  • Configurable: Set via SNACKBASE_PERMISSION_CACHE_TTL_SECONDS
  • Automatic Invalidation: Cache is cleared when permissions change

Cache Key Format

{user_id}:{collection}:{operation}
Example: user_123:posts:read

API Endpoints

Create Permission

POST /api/v1/permissions
Request Body:
{
  "role_id": 123,
  "collection": "posts",
  "rules": {
    "create": {
      "rule": "user.role == 'admin'",
      "fields": ["title", "content"]
    },
    "read": {
      "rule": "true",
      "fields": "*"
    }
  }
}

List Permissions

GET /api/v1/permissions

Delete Permission

DELETE /api/v1/permissions/{permission_id}

Common Patterns

1. Public Read Access

Allow anyone to read records, but only admins to modify.
OperationRule
readtrue
create/update/deleteuser.role == "admin"

2. Owner-Only Access

Users can only manage their own data.
OperationRule
createtrue
read/update/deleteuser.id == record.created_by
Alternative Macro@is_creator()

3. Group-Based Access

OperationRule
read"managers" in user.groups
Alternative Macro@has_group("managers")

4. Status-Based Workflow

Only allow updates if the record is in a specific state.
# Allow update strictly if status is 'draft' OR user is admin
(record.status == 'draft' and user.id == record.created_by) or user.role == 'admin'

5. Field-Level Restrictions

Role: employee
  • Operation: read
  • Rule: user.id == record.user_id
  • Allowed Fields: ["id", "name", "department"] (Exclude salary)
Role: hr_manager
  • Operation: read
  • Rule: true
  • Allowed Fields: * (All fields)

Best Practices

1. Deny by Default

If no rule is defined, access is denied. Only define allow rules.

2. Use Macros for Reusability

For complex logic repeated across collections, wrap it in a SQL Macro.

3. Keep Rules Simple

Complex rules impact performance. Break complex logic into SQL Macros if needed.

4. Always Use Field Filtering

Always use field limiting for sensitive data sets. Don’t rely solely on UI hiding.

5. Test Your Rules

Use the admin UI’s rule tester or create test records to verify your rules.

6. Leverage Caching

Permission results are cached for 5 minutes. Changes take effect within this window.