Skip to content
Zap Studio
permit
Esc
navigateopen⌘Jpreview
On this page

permit

A type-safe, declarative authorization library for TypeScript with Standard Schema validation and composable conditions.

permit is a type-safe, declarative authorization library for TypeScript with Standard Schema support.

Motivation

Authorization checks written by hand, like if (user.role === "admin"), spread through a codebase over time. After a while, nobody can answer “who is allowed to delete a post?” without searching the whole app.

A framework like CASL solves the spreading problem, but it comes with its own vocabulary to learn (subject, can, cannot, rules), and its rules are not checked against your actual data shapes — you can write a rule that references a field your resource does not have, and it will only fail once that code runs.

permit keeps all rules in one place, through createPolicy(...) with allow(), deny(), and when(condition) — one file answers “who can do what.”

And because resources come from your Standard Schema schemas, policy types are derived straight from your real data shapes. Reference a field that does not exist, and you get an error while writing the code, not a silent undefined in production.

Features

  • Full type safety — actions, resources, and permissions are inferred from your schemas and satisfies declarations, from Getting Started onward.
  • Standard Schema support via Resources — works with Zod, Valibot, ArkType, or any compatible library. See Standard Schema Support.
  • Declarative policies through createPolicy(...) with allow(), deny(), and when(condition). See Declarative Policies.
  • Role hierarchy support via hasRole(role, hierarchy?), with inheritance resolved by collectInheritedRoles. See Role-Based Access Control.
  • Composable conditions via and, or, and not. See Conditions.
  • Policy merging strategies via mergePoliciesAnd and mergePoliciesOr. See Merging Policies.
  • Structured errors with PolicyError for invalid configuration or evaluation failures. See Error Handling.
  • Optional logging through createPolicy({ logger }) from logger — omit it and there’s zero added logging overhead.
  • Native OpenTelemetry — an INTERNAL span per check with the allow/deny decision as an attribute, plus a permit.checks counter, no-op until an SDK is registered.
  • Tree-shakeable — policies and conditions are plain functions; unused exports are dropped by any modern bundler.

Quick Start

import { z } from "zod";
import { ConsoleLogger } from "@zap-studio/logger";
import { createPolicy, allow, deny, when } from "@zap-studio/permit";
import type { Resources, Actions } from "@zap-studio/permit";

const resources = {
  post: z.object({ id: z.string(), authorId: z.string() }),
} satisfies Resources;

const actions = {
  post: ["read", "write", "delete"],
} as const satisfies Actions<typeof resources>;

type AppContext = { user: { id: string } };

const logger = new ConsoleLogger({ minLevel: "debug" });

const policy = createPolicy<AppContext>({
  resources,
  actions,
  rules: {
    post: {
      read: allow(),
      write: when((ctx, action, resource) => ctx.user.id === resource.authorId),
      delete: deny(),
    },
  },
  logger,
});

const ctx: AppContext = { user: { id: "user-1" } };
const post = { id: "1", authorId: "user-1" };

await policy.can(ctx, "post:write", post); // true, inferred as boolean

Continue with Getting Started for a full walkthrough of resources, actions, context, and checking permissions.

Runtime Support

Runtime Minimum version
Node.js 18.0.0
Bun 1.0.0
Deno 1.42
Cloudflare Workers Any current release
Browsers Latest evergreen (Chrome, Edge, Firefox, Safari)

The package ships standard ESM only and uses no runtime-specific APIs. Deno 1.42 is the first release that can install packages from JSR (deno add jsr:@zap-studio/permit).

Last updated on September 15, 2026

Was this page helpful?