Skip to content

Permissions and patterns

Permissions are dot-separated segments, for example workspaces.create, workspaces.1.read, or posts.comments.delete. Segments may contain any character except ., and a permission cannot have empty segments:

^[^.]+(?:\.[^.]+)*$ // PermissionSchema

Granted permissions act as patterns: a granted permission implies a requested one when every segment matches. A segment is a match when it is equal, or when the granted segment is * (matches any single segment). A trailing * matches any number of remaining segments, including none.

Granted Requested Allowed
workspaces.1.read workspaces.1.read yes
workspaces.1.* workspaces.1.read yes
workspaces.*.read workspaces.7.read yes
workspaces.* workspaces.7.create yes
workspaces.1.* workspaces.2.read no
workspaces.1 workspaces.1.read no

The matcher is exported from @vperms/core (and re-exported by vperms):

import { matchesPattern } from "vperms";
matchesPattern("workspaces.1.*", "workspaces.1.read"); // true
matchesPattern("workspaces.*.read", "workspaces.7.read"); // true
matchesPattern("workspaces.1.*", "workspaces.2.read"); // false

matchesPattern(grantPattern, permission) is a pure, synchronous segment comparison. It performs no inheritance and knows nothing about grants or denies.

When several grants match the same requested permission, the engine must decide which one wins. It scores each pattern with permissionSpecificity:

  • an exact segment adds 2,
  • an inner * adds 1,
  • a trailing * adds 0.
import { permissionSpecificity } from "vperms";
permissionSpecificity("workspaces.1.read"); // 6
permissionSpecificity("workspaces.*.read"); // 5
permissionSpecificity("workspaces.1.*"); // 4
permissionSpecificity("workspaces.*"); // 2

More specific patterns win over broader wildcards. See Resolution for the full precedence order and how specificity combines with inheritance depth and source layer.

matchPermission(grants, permission) returns the value of the best matching grant, or null when nothing matches:

import { matchPermission } from "vperms";
const grants = [
{ permission: "workspaces.*", value: true },
{ permission: "workspaces.1.*", value: false },
];
matchPermission(grants, "workspaces.1.read"); // false (more specific deny)
matchPermission(grants, "workspaces.2.read"); // true
matchPermission(grants, "posts.read"); // null

Grants are sorted with compareGrants first, so the result does not depend on the order of the array.

VeguiPerms reserves a small permission namespace for reading resolved permissions:

  • vperms.subject.me.permissions — always granted to every subject (at the lowest priority, so a deny can override it). Lets a subject read its own resolved permission set.
  • vperms.subject.<subjectId>.permissions — permission to read another subject’s resolved set. Normal wildcards apply, e.g. vperms.subject.*.permissions.
import { subjectPermissionsPermission } from "vperms";
subjectPermissionsPermission("user"); // "vperms.subject.user.permissions"

See Exporting resolved permissions.