---
title: Getting Started
description: Define resources, actions, context, and rules to build your first authorization policy with permit.
type: package
package: "@zap-studio/permit"
---

A policy is the core building block of `permit`. It defines what actions users can perform on your application's resources based on the current context.

## Installation

<CodeGroup>

```bash npm
npm install @zap-studio/permit
```

```bash yarn
yarn add @zap-studio/permit
```

```bash pnpm
pnpm add @zap-studio/permit
```

```bash bun
bun add @zap-studio/permit
```

```bash deno
deno add jsr:@zap-studio/permit
```

</CodeGroup>

You also need a schema library that implements [Standard Schema](https://standardschema.dev/), such as Zod, Valibot, or ArkType.

## Understanding Policies

Think of a policy as a rulebook that answers questions like:

- "Can this user edit this blog post?"
- "Can a guest view this private document?"
- "Can an admin delete any comment?"

A policy consists of three parts:

1. **Resources** — The things being protected (posts, comments, users)
2. **Actions** — What can be done with resources (read, write, delete)
3. **Rules** — The logic that determines if an action is allowed

:::info

Define resources and actions in a centralized location to ensure consistency and type safety across your application.

:::

## Defining Resources

Resources are defined using [Standard Schema](https://standardschema.dev/), which means you can use Zod, Valibot, ArkType, or any compatible library.

```ts
import { z } from "zod";
import type { Resources } from "@zap-studio/permit";

const resources = {
  post: z.object({
    id: z.string(),
    authorId: z.string(),
    title: z.string(),
    visibility: z.enum(["public", "private", "draft"]),
    createdAt: z.date(),
  }),
  comment: z.object({
    id: z.string(),
    postId: z.string(),
    authorId: z.string(),
    content: z.string(),
  }),
  user: z.object({
    id: z.string(),
    email: z.email(),
    role: z.enum(["guest", "user", "admin"]),
  }),
} satisfies Resources;
```

See [Standard Schema Support](/permit/standard-schema) for the same definition with Valibot and ArkType.

:::warning

Use `satisfies` to ensure type safety and consistency. Learn more about the [TypeScript `satisfies` operator](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator).

:::

## Defining Actions

Actions specify what operations are allowed on each resource. Define them as readonly arrays:

```ts
import type { Actions } from "@zap-studio/permit";

const actions = {
  post: ["read", "write", "delete", "publish"],
  comment: ["read", "write", "delete"],
  user: ["read", "update", "delete", "ban"],
} as const satisfies Actions<typeof resources>;
```

:::warning

Use `as const` so TypeScript infers literal types for actions. This enables autocomplete and type checking when writing rules and permission strings. Learn more about [`const` assertions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions).

:::

## Understanding Context

Context represents the runtime information available when checking permissions. This typically includes the current user, but can contain anything relevant to authorization:

```ts
type AppContext = {
  user: {
    id: string;
    role: "guest" | "user" | "admin";
    permissions: string[];
    organizationId: string;
  } | null;
  request?: {
    ip: string;
    userAgent: string;
  };
  timestamp: Date;
};
```

Context is passed to `policy.can()` at runtime and is available in all your rule functions.

:::info

Context is just a type — it can be anything relevant to your application's authorization needs. Include all the data your rules need so they never have to fetch anything.

:::

## Creating a Policy

Use `createPolicy()` to combine your resources, actions, and rules into a policy. See [Declarative Policies](/permit/declarative-policies) for the full reference on `allow()`, `deny()`, and `when()`.

```ts
import { createPolicy, allow, deny, when } from "@zap-studio/permit";

const policy = createPolicy<AppContext>({
  resources,
  actions,
  rules: {
    post: {
      read: allow(),
      write: when((ctx, _action, post) => ctx.user?.id === post.authorId),
      delete: deny(),
      publish: when((ctx) => ctx.user?.role === "admin"),
    },
    comment: {
      read: allow(),
      write: when((ctx) => ctx.user !== null),
      delete: when((ctx, _action, comment) => ctx.user?.id === comment.authorId),
    },
  },
});
```

:::note

`createPolicy()` throws a [`PolicyError`](/permit/errors) at creation time if a resource key is missing its schema. Rules for individual actions are optional — an action without a rule is always denied.

:::

## Checking Permissions

The policy object provides a `can()` method. It takes the `context`, a `"resource:action"` permission string, and the `resource`, then returns a `Promise<boolean>`:

```ts
policy.can(context, permission, resource): Promise<boolean>
```

### Parameters

| Parameter    | Type       | Description                                                      |
| ------------ | ---------- | ---------------------------------------------------------------- |
| `context`    | `TContext` | The current context (user, request, etc.)                        |
| `permission` | `string`   | The permission to check (e.g., `"post:read"`, `"comment:write"`) |
| `resource`   | `object`   | The actual resource being accessed                               |

### How can() Decides

`can()` never throws — it resolves to `false` whenever a check fails:

1. **Parse the permission** — Malformed strings (missing or extra `:` segments) return `false`.
2. **Check the action** — Actions not listed for the resource in `actions` return `false`.
3. **Validate the resource** — The resource is validated against its Standard Schema; invalid resources return `false`.
4. **Evaluate the rule** — No rule defined for the action returns `false`. If the rule throws, the error is logged with `console.warn` and `can()` returns `false`.

## Real-World Example: E-commerce Store

Here's a complete example for an e-commerce application:

```ts
import { z } from "zod";
import { createPolicy, allow, when, or } from "@zap-studio/permit";
import type { Resources, Actions } from "@zap-studio/permit";

// Define resources
const resources = {
  product: z.object({
    id: z.string(),
    sellerId: z.string(),
    price: z.number(),
    status: z.enum(["draft", "published", "archived"]),
  }),
  order: z.object({
    id: z.string(),
    customerId: z.string(),
    sellerId: z.string(),
    status: z.enum(["pending", "paid", "shipped", "delivered"]),
  }),
  review: z.object({
    id: z.string(),
    productId: z.string(),
    customerId: z.string(),
    rating: z.number().min(1).max(5),
  }),
} satisfies Resources;

const actions = {
  product: ["read", "create", "update", "delete", "publish"],
  order: ["read", "create", "update", "cancel"],
  review: ["read", "create", "update", "delete"],
} as const satisfies Actions<typeof resources>;

type StoreContext = {
  user: {
    id: string;
    role: "customer" | "seller" | "admin";
  } | null;
};

const storePolicy = createPolicy<StoreContext>({
  resources,
  actions,
  rules: {
    product: {
      // Anyone can read published products
      read: when((_, __, product) => product.status === "published"),
      // Only sellers can create products
      create: when((ctx) => ctx.user?.role === "seller"),
      // Sellers can update their own products
      update: when(
        (ctx, _, product) => ctx.user?.role === "seller" && ctx.user.id === product.sellerId,
      ),
      // Only admins can delete products
      delete: when((ctx) => ctx.user?.role === "admin"),
      // Sellers can publish their own products
      publish: when(
        (ctx, _, product) => ctx.user?.role === "seller" && ctx.user.id === product.sellerId,
      ),
    },
    order: {
      // Customers see their orders, sellers see orders for their products
      read: when(
        or(
          (ctx, _, order) => ctx.user?.id === order.customerId,
          (ctx, _, order) => ctx.user?.id === order.sellerId,
        ),
      ),
      // Only authenticated customers can create orders
      create: when((ctx) => ctx.user?.role === "customer"),
      // Sellers can update order status
      update: when((ctx, _, order) => ctx.user?.id === order.sellerId),
      // Customers can cancel pending orders
      cancel: when(
        (ctx, _, order) => ctx.user?.id === order.customerId && order.status === "pending",
      ),
    },
    review: {
      // Anyone can read reviews
      read: allow(),
      // Customers can create reviews
      create: when((ctx) => ctx.user?.role === "customer"),
      // Customers can update their own reviews
      update: when((ctx, _, review) => ctx.user?.id === review.customerId),
      // Customers can delete their reviews, admins can delete any
      delete: when(
        or(
          (ctx, _, review) => ctx.user?.id === review.customerId,
          (ctx) => ctx.user?.role === "admin",
        ),
      ),
    },
  },
});

// Usage
const product = {
  id: "prod-1",
  sellerId: "seller-123",
  price: 99.99,
  status: "published" as const,
};

const customerContext: StoreContext = {
  user: { id: "customer-456", role: "customer" },
};

console.log(await storePolicy.can(customerContext, "product:read", product)); // true
console.log(await storePolicy.can(customerContext, "product:update", product)); // false
```

## Best Practices

1. **Keep resources focused** — Each resource should represent a single domain entity
2. **Use descriptive action names** — "publish" is clearer than "update-status"
3. **Include all relevant data in context** — Don't fetch additional data inside rules
4. **Be explicit about what's allowed** — Actions without a rule are denied by default
5. **Test your policies** — Write unit tests for critical authorization rules
