Skip to content

Access - Role & Condition-Based Access Control

Define who can perform CRUD operations and under what conditions.

Configuration Options

typescript
interface Access {
  // Required roles (OR logic - user needs at least one)
  roles?: string[];

  // Record-level conditions
  record?: {
    [field: string]: FieldCondition;
  };

  // Combinators
  or?: Access[];
  and?: Access[];
}

// Field conditions
type FieldCondition =
  | { equals: value | '$ctx.userId' | '$ctx.activeOrgId' }
  | { notEquals: value }
  | { in: value[] }
  | { notIn: value[] }
  | { lessThan: number }
  | { greaterThan: number }
  | { lessThanOrEqual: number }
  | { greaterThanOrEqual: number };

CRUD Configuration

typescript
crud: {
  // LIST - GET /resource
  list: {
    access: { roles: ["member", "admin"] },
    pageSize: 25,        // Default page size
    maxPageSize: 100,    // Client can't exceed this
    fields: ['id', 'name', 'status'],  // Selective field returns (optional)
  },

  // GET - GET /resource/:id
  get: {
    access: { roles: ["member", "admin"] },
    fields: ['id', 'name', 'status', 'details'],  // Optional field selection
  },

  // CREATE - POST /resource
  create: {
    access: { roles: ["member", "admin"] },
    defaults: {           // Default values for new records
      status: 'pending',
      isActive: true,
    },
  },

  // UPDATE - PATCH /resource/:id
  update: {
    access: {
      or: [
        { roles: ["admin"] },
        { roles: ["member"], record: { status: { equals: "draft" } } }
      ]
    },
  },

  // DELETE - DELETE /resource/:id
  delete: {
    access: { roles: ["admin"] },
    mode: "soft",  // 'soft' (default) or 'hard'
  },

  // PUT - PUT /resource/:id (only when generateId: false + guards: false)
  put: {
    access: { roles: ["admin", "sync-service"] },
  },
}

List Filtering (Query Parameters)

The LIST endpoint automatically supports filtering via query params:

GET /rooms?status=active                    # Exact match
GET /rooms?capacity.gt=10                   # Greater than
GET /rooms?capacity.gte=10                  # Greater than or equal
GET /rooms?capacity.lt=50                   # Less than
GET /rooms?capacity.lte=50                  # Less than or equal
GET /rooms?status.ne=deleted                # Not equal
GET /rooms?name.like=Conference             # Pattern match (LIKE %value%)
GET /rooms?status.in=active,pending,review  # IN clause
OperatorQuery ParamSQL Equivalent
Equals?field=valueWHERE field = value
Not equals?field.ne=valueWHERE field != value
Greater than?field.gt=valueWHERE field > value
Greater or equal?field.gte=valueWHERE field >= value
Less than?field.lt=valueWHERE field < value
Less or equal?field.lte=valueWHERE field <= value
Pattern match?field.like=valueWHERE field LIKE '%value%'
In list?field.in=a,b,cWHERE field IN ('a','b','c')

Sorting & Pagination

GET /rooms?sort=createdAt&order=desc   # Sort by field
GET /rooms?limit=25&offset=50          # Pagination
  • Default limit: 50
  • Max limit: 100 (or maxPageSize if configured)
  • Default order: asc

Delete Modes

typescript
delete: {
  access: { roles: ["admin"] },
  mode: "soft",  // Sets deletedAt/deletedBy, record stays in DB
}

delete: {
  access: { roles: ["admin"] },
  mode: "hard",  // Permanent deletion from database
}

Context Variables

Use $ctx. prefix to reference context values in conditions:

typescript
// User can only view their own records
access: {
  record: { userId: { equals: "$ctx.userId" } }
}

// Available context variables:
// $ctx.userId        - Current user's ID
// $ctx.activeOrgId   - Current organization ID
// $ctx.activeTeamId  - Current team ID
// $ctx.{property}    - Any custom context property

Backend security, simplified.