Moul

Access Rules & Filter Expressions

Enforce row-level security, role-based access, and field constraints with HCL-like rule expressions.

Every dynamic collection in Moul is guarded by five row-level access control rules evaluated dynamically on incoming client requests:

  • listRule — Restricts which records are returned in collection list queries.
  • viewRule — Controls access to view a single record by ID.
  • createRule — Validates incoming fields and permissions before record insertion.
  • updateRule — Validates current record fields and incoming values before update.
  • deleteRule — Validates permissions before record deletion.

Rule Access States

ValueAccess State
nullAdmin Only: Only requests carrying a valid master X-Admin-Key header can perform this operation. Public and user requests receive HTTP 403 Forbidden.
"" (Empty string)Public: Anyone (including unauthenticated guests) can perform this operation.
"expression..."Conditional: The operation is allowed only if the expression evaluates to true against the request context and record state.

Expression Syntax Reference

Context Variables

  • @request.auth.id: Authenticated user's unique record ID.
  • @request.auth.email: Authenticated user's email.
  • @request.auth.*: Any custom field present on the authenticated user's record.
  • @request.body.fieldName: Value submitted in incoming JSON request body.
  • @request.headers.header_name: Incoming HTTP request header value.
  • @request.query.paramName: URL query parameter value.
  • @request.method: HTTP request method (GET, POST, PATCH, DELETE).

Operators

OperatorMeaningExample
=Equal tostatus = 'published'
!=Not equal to@request.auth.id != ''
>, >=Greater than / Greater than or equalviews >= 100
<, <=Less than / Less than or equalage < 18
~Contains / LIKE substring matchtitle ~ 'tutorial'
!~Does not contain substringemail !~ '@spam.com'
&&, ||Logical AND / Logical ORpublished = true || authorId = @request.auth.id
?=Array modifier (contains element)collaboratorIds.id ?= @request.auth.id

Field Modifiers

  • :lower: Converts string to lowercase for case-insensitive comparison (e.g. email:lower = @request.body.email:lower).
  • :length: Returns length of string or array (e.g. content:length > 20).
  • :isset: Checks if a field was provided in request body (e.g. @request.body.password:isset = true).
  • :changed: Checks if field value is being modified in an update request (e.g. @request.body.role:changed = false).
  • :each: Evaluates condition across all elements of an array.

Helper Functions

  • geoDistance(lon1, lat1, lon2, lat2): Calculates distance in kilometers between two geographic coordinates.
  • strftime(format, timestamp): Evaluates SQLite datetime string formatting.

Cross-Collection Join Queries

Access rules can query other collections dynamically using @collection syntax:

@collection.userRoles.userId = @request.auth.id && @collection.userRoles.role = 'admin'

Common Rule Recipes

1. Public Read, Authenticated Write

listRule:   ""
viewRule:   ""
createRule: "@request.auth.id != ''"
updateRule: "authorId = @request.auth.id"
deleteRule: "authorId = @request.auth.id"

2. Multi-Tenant Organization Isolation

listRule:   "orgId = @request.auth.orgId"
viewRule:   "orgId = @request.auth.orgId"
createRule: "@request.body.orgId = @request.auth.orgId"
updateRule: "orgId = @request.auth.orgId"
deleteRule: "orgId = @request.auth.orgId && @request.auth.role = 'org_admin'"

3. Role-Based Access Control: users Auth Collection

Configure the users auth collection with custom fields:

  • role: Type select, Options: ["admin", "editor", "public"], Required: true
  • Optional profile fields: name (text), avatar (url or file), bio (text)

Access rules prevent privilege escalation while enabling self-service profile management and administrative oversight:

listRule:   "id = @request.auth.id || @collection.users.id = @request.auth.id && (@collection.users.role = 'editor' || @collection.users.role = 'admin')"
viewRule:   "id = @request.auth.id || @collection.users.id = @request.auth.id && (@collection.users.role = 'editor' || @collection.users.role = 'admin')"
createRule: "@request.body.role:isset = false || @request.body.role = 'public' || (@collection.users.id = @request.auth.id && @collection.users.role = 'admin')"
updateRule: "(@collection.users.id = @request.auth.id && @collection.users.role = 'admin') || (id = @request.auth.id && @request.body.role:changed = false)"
deleteRule: "(@collection.users.id = @request.auth.id && @collection.users.role = 'admin') || id = @request.auth.id"
  • createRule: Allows open registration with the public role (or omitting the field to accept default), while preventing unauthorized users from assigning themselves editor or admin. Only existing admins can provision elevated roles directly.
  • updateRule: Users can update their own profile fields, but cannot modify their assigned role (@request.body.role:changed = false). Only an admin can promote or demote user roles.
  • deleteRule: Users can self-delete their own account (id = @request.auth.id), while an admin can delete any user record.

4. Enforcing Role-Level Access on Downstream Collections (e.g. posts)

Downstream business collections query the users auth collection using @collection relational lookups to enforce role-level privileges (public | editor | admin):

  • authorId: Type relation targeting users (1:1)
  • status: Type select, Options: ["draft", "published", "archived"]
listRule:   "status = 'published' || (@collection.users.id = @request.auth.id && (@collection.users.role = 'editor' || @collection.users.role = 'admin'))"
viewRule:   "status = 'published' || (@collection.users.id = @request.auth.id && (@collection.users.role = 'editor' || @collection.users.role = 'admin'))"
createRule: "@collection.users.id = @request.auth.id && (@collection.users.role = 'editor' || @collection.users.role = 'admin')"
updateRule: "(@collection.users.id = @request.auth.id && @collection.users.role = 'admin') || (authorId = @request.auth.id && @collection.users.id = @request.auth.id && @collection.users.role = 'editor')"
deleteRule: "@collection.users.id = @request.auth.id && @collection.users.role = 'admin'"
  • Public: Anyone can list and view records where status = 'published'.
  • Editor: Can view unpublished drafts, create new posts, and edit posts where they are the designated author (authorId = @request.auth.id).
  • Admin: Has full access to view, create, edit, publish, and delete all records.

Testing Rules via CLI Sandbox

You can test and benchmark rules locally without executing live HTTP requests:

moul test-rule \
  --rule="authorId = @request.auth.id && views > 100" \
  --record='{"authorId": "usr_001", "views": 250}' \
  --auth='{"id": "usr_001"}'

On this page