Reference

Access Control

Access control and permissions in dnax Framework.

Access control defines who can perform which actions on a collection. You configure it with api.access on each collection model.

Request pipeline and JWT

If the client sends Authorization: Bearer <jwt>, the server verifies the JWT first. A bad token yields 401 before api.access runs. If there is no Bearer header, verification is skipped; access functions still run, with token.value and token.decoded set to null when unauthenticated.

How api.access is structured

  • Keys — Built-in action names (for example find, insertOne, deleteOne, aggregate, login, logout, …), the wildcard '*', or a custom string matching a named action on the same collection.
  • Values — Either a boolean, or a function (usually async) that returns whether the action is allowed.

Configure access rules

import { define } from '@dnax/core';

export default define.Collection({
  slug: 'users',
  fields: [
    { name: 'name', type: 'string' },
  ],
  api: {
    access: {
      // Wildcard — applies when no per-action rule is set for that action
      '*': true,

      // Specific built-in action
      deleteOne: false,

      // Dynamic check using verified JWT claims
      updateOne: async ({ token }) => {
        return token?.decoded?.role === 'admin';
      },
    },
  },
});

Access rule types

TypeExampleDescription
booleantrue | falseStatic allow or deny
functionasync (ctx) => boolean (or Promise<boolean>)Decide at runtime using rest, jwt, token, etc.

Wildcard (*)

Use '*' as a default when you do not define a rule for a specific action:

api: {
  access: {
    '*': true,   // allow by default (use false to deny by default)
  },
},

Per-action access

Control built-in CRUD/query actions individually:

api: {
  access: {
    find: true,
    findOne: true,
    insertOne: async ({ token }) => {
      return token?.decoded?.role === 'admin';
    },
    updateOne: async ({ token }) => {
      return token?.decoded?.role === 'admin';
    },
    deleteOne: false,
    deleteMany: false,
  },
},

Access handler context

Access functions receive rest (tenant database access), error (throw structured failures, same pattern as hooks), jwt (sign / verify), and token:

{
  rest: useRest;
  error: (message: string, options?: object) => never;
  jwt: {
    sign: (payload, options?) => Promise<string>;
    verify: (token: string) => Promise<{ value: Record<string, unknown> | null; error: string | null }>;
  };
  token: {
    value: string | null;
    decoded: Record<string, unknown> | null;
  };
}

Use token.decoded for role checks, user id (sub), or any claim you issued in onLogin. token.value is the original Bearer string if you need to forward or re-verify it.

Example: user roles

api: {
  access: {
    '*': async ({ token }) => {
      return !!token?.decoded;
    },

    deleteOne: async ({ token }) => {
      return token?.decoded?.role === 'admin';
    },
  },
},

Example: owner-only edit

api: {
  access: {
    updateOne: async ({ jwt, rest, token }) => {
      const subject = token?.decoded?.sub as string | undefined;
      if (!subject) return false;

      const doc = await rest.findOne('users', subject);
      return doc?._id === subject;
    },
  },
},

(Adjust the id field to match how you store the subject in your documents.)

Private fields

Hide fields from responses:

api: {
  privateFields: [
    'password',
    'internalNote',
    /^secret_/,
  ],
},

Fields matching these patterns are removed from all API responses.

Read-only fields

Prevent updates to certain fields:

api: {
  readOnlyFields: [
    'createdAt',
    'createdBy',
    'role',
  ],
},

These fields cannot be modified via insertOne, insertMany, updateOne, or updateMany.

Copyright © 2026