Access control

Basics

Configure role-based access control with document and field-level permissions.

Mongalayer's access control system lets you define roles with filters, document-level permissions, field-level permissions, delete permissions, and validators. Each request is evaluated against these definitions to determine what data the caller can access.

Access definitions

An access configuration is an array of AccessDefinition objects, one per role:

import { AccessPermissions, type AccessConfig, type AccessDefinition } from "@mongalayer/server";

// As a single definition object:
const memberProjectAccess: AccessDefinition<Project> = {
    role: "member",
    filter: {
        members: { "$in": ["%%user.id"] }
    },
    document: AccessPermissions.Read
};

// Directly in the access array:
export const projectAccess: AccessConfig<Project> = [{
    role: "owner",
    filter: {
        owner: "%%user.id"
    },
    document: AccessPermissions.ReadWrite,
    delete: true
}, {
    role: "contributor",
    filter: {
        editors: { "$in": ["%%user.id"] }
    },
    document: AccessPermissions.Read,
    fields: {
        title: AccessPermissions.ReadUpdate,
        description: AccessPermissions.ReadUpdate
    },
},
// Add the member role from before
memberProjectAccess
];

Properties

PropertyTypeDescription
rolestringA unique name identifying this role
filterAccessFilterA filter that determines which documents this role applies to
documentAccessPermissionThe default permission level for the entire document
fieldsRecord<string, AccessPermission>Per-field permission overrides, only root level fields
deletebooleanWhether this role allows deleting documents
collectionAccessAlternativeCollectionUse an alternative collection for role resolution
validatorsobjectCustom validation functions for create and update operations

Access permissions

Permissions are defined using bitwise flags:

import { AccessPermissions } from "@mongalayer/server";

AccessPermissions.None       // 0      — no access
AccessPermissions.Read       // 0b0001 — read only
AccessPermissions.Create     // 0b0010 — create only
AccessPermissions.Update     // 0b0100 — update only
AccessPermissions.ReadUpdate // 0b0101 — read + update (no create)
AccessPermissions.ReadWrite  // 0b0111 — read + create + update

You can combine permissions using bitwise operators:

// Read + Create, but no Update
const permission = AccessPermissions.Read | AccessPermissions.Create;

// ReadWrite without Update
const permission = AccessPermissions.ReadWrite & ~AccessPermissions.Update;
// or equivalently:
const permission = AccessPermissions.ReadWrite ^ AccessPermissions.Update;

Access filters

Filters determine which documents a role applies to. They use a subset of MongoDB's query syntax with support for hydration — injecting values from the access payload at runtime.

Hydration with %%

Any string value prefixed with %% is replaced with the corresponding value from the access payload:

For more info on the access payload, see the server configuration.
// Access payload: { user: { id: "user-123", org: "org-456" } }

{
    role: "self",
    filter: {
        _id: "%%user.id"           // becomes: { _id: "user-123" }
    }
}

{
    role: "org-member",
    filter: {
        organizationId: "%%user.org" // becomes: { organizationId: "org-456" }
    }
}

Supported filter operators

Property-level operators:

OperatorDescription
$eqEquals
$neNot equals
$inIn array
$ninNot in array
$existsField exists

Root-level operators:

OperatorDescription
$andAll conditions must match
$orAt least one condition must match
$norNone of the conditions must match

Custom operators (access filters only):

OperatorDescription
$$eqCompare two values for equality
$$inCheck if a value is in an array
$$neCompare two values for inequality
$$ninCheck if a value is not in an array

Examples

// Simple equality
{ owner: "%%user.id" }

// Array membership
{ members: { "$in": ["%%user.id"] } }

// Multiple conditions - Owner or contributor
{
    "$or": [
        { "owner": { "$eq": "%%user.id" } },
        { "contributors": { "$in": ["%%user.id"] } }
    ]
}

// Custom equality operator - match specific user
{
    "$$eq": ["%%user.id", "ABC"]
}

// Custom includes operator - check if user has admin role
{
    "$$in": [ "admin", "%%user.roles" ]
}

// Documents filter - exclude archived documents
{
    "$or": [
        { "archived": { "$exists": false } },
        { "archived": { "$ne": true } }
    ]
}

Field-level permissions

The fields property lets you override the document-level permission for specific top-level fields:

{
    role: "member",
    document: AccessPermissions.Read,       // Default: read-only
    fields: {
        config: AccessPermissions.None,       // config field: completely hidden
        name: AccessPermissions.ReadWrite,    // name field: read + write
        description: AccessPermissions.ReadUpdate  // description: read + update (no create)
        createdAt: AccessPermissions.Read | AccessPermissions.Create, // createdAt: read + create
    }
}

When a field is not listed in fields, it inherits the document permission. When document is not set on the role, it falls back to accessDefaults.document option of the Mongalayer instance configuration as shown below.

Field-level permissions only support root-level properties from the document schema.

Default permissions

When creating a Mongalayer instance, you can set default permissions that apply when no role matches or when a role doesn't explicitly set a permission:

const layer = new Mongalayer(mongoClient, collections, {
  accessDefaults: {
    document: AccessPermissions.Read,  // Default: read-only
    delete: false                      // Default: no deletion
  }
});

Role resolution

This is how Mongalayer internally determines which role applies to each document

When a query is executed, Mongalayer determines which role applies to each document by evaluating the access filters. A __mongalayer_role field is temporarily added to each document during processing to track the matched role.

  • If no roles are defined (empty access array), all documents are accessible with default permissions
  • If roles are defined but no role matches a document, the document is inaccessible
  • If multiple roles could match, the first matching role in the array is used

The order of roles in your access configuration matters — put the role with the least permissions last. For example: admin -> owner -> contributor -> member

Copyright © 2026