Skip to content

Getting started

Rolebook is a private preview and is not yet on a public registry. Once publishing is enabled:

Terminal window
bun add rolebook

The package is plain ESM with TypeScript declarations and runs on any modern JavaScript runtime.

Resources declare their concrete actions and one optional parent type. Roles live at a scope and hold allow and deny rules. Define the policy once and reuse the returned object: it is immutable, and its compiled form is cached by identity.

import { definePolicy } from "rolebook";
export const policy = definePolicy({
version: "4.0.0",
resources: {
organization: { actions: ["read"] },
workspace: { parent: "organization", actions: ["read", "update"] },
project: { parent: "workspace", actions: ["read", "update", "delete"] },
},
roles: {
workspace_editor: {
scope: "workspace",
rules: [
{ effect: "allow", permission: "project:read" },
{ effect: "allow", permission: "project:update" },
],
},
},
});

Bind the authenticated subject together with the scoped assignments your application loaded for this request. The subject never carries roles.

import { createAuthorizer } from "rolebook";
const authorization = createAuthorizer(policy).bind({
subject: { type: "user", id: "user_123" },
assignments: [
{
subject: { type: "user", id: "user_123" },
role: "workspace_editor",
scope: {
resource: { type: "workspace", id: "ws_456" },
appliesTo: "descendants",
},
},
],
});

Load the resource through your authoritative, tenant-scoped path, then snapshot it with its complete ancestry, from immediate parent to root.

import { trustedResource } from "rolebook";
const project = trustedResource(
{ type: "project", id: "proj_1" },
[
{ type: "workspace", id: "ws_456" },
{ type: "organization", id: "org_1" },
],
{ ownerId: "user_123", status: "draft" },
);
const decision = authorization.check({ permission: "project:update", resource: project });
decision.allowed; // true
decision.code; // "ALLOWED"

check() never throws for malformed input. It returns a closed decision with a stable code instead. Use enforce() to throw on denial and explain() for a rule trace. See Decisions.