---
title: Declarative Policies
description: "Through createPolicy(...) with allow(), deny(), and when(condition) — define authorization rules in permit."
type: package
package: "@zap-studio/permit"
---

`createPolicy(...)` builds policies using `allow()`, `deny()`, and `when(condition)`. `permit` provides three rule builders for defining what's allowed.

## Understanding Rules

Every rule returns a **decision**: either `"allow"` or `"deny"`. Rules receive three arguments:

1. **context** — The current user/request context
2. **action** — The action being performed (e.g., `"read"`, `"write"`)
3. **resource** — The resource being accessed (already validated against its schema)

:::note

Rules are optional per action. If an action has no rule, `policy.can()` returns `false` for it. If a rule throws, the error is caught, logged with `console.warn`, and the action is denied.

:::

## Setup

The examples on this page use these definitions:

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

const resources = {
  post: z.object({
    id: z.string(),
    authorId: z.string(),
    visibility: z.enum(["public", "private"]),
  }),
} satisfies Resources;

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

type AppContext = {
  user: { id: string; role: "guest" | "user" | "admin" } | null;
};
```

## allow()

The `allow()` function creates a rule that always permits the action, regardless of context or resource.

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

### When to Use

Use `allow()` for actions that should be available to everyone:

- Public content (published blog posts, product listings)
- Health check endpoints
- Public API documentation

### Example: Public Content

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

const policy = createPolicy<AppContext>({
  resources,
  actions,
  rules: {
    post: {
      // Anyone can read posts (refine this with conditions later)
      read: allow(),
    },
  },
});
```

## deny()

The `deny()` function creates a rule that always blocks the action. No context or resource can override this.

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

### When to Use

Use `deny()` for:

- Temporarily disabled features
- Actions reserved for future implementation
- Hard blocks that should never be bypassed

### Example: Disabled Actions

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

const policy = createPolicy<AppContext>({
  resources,
  actions,
  rules: {
    post: {
      read: allow(),
      write: when((ctx, _, post) => ctx.user?.id === post.authorId),
      // Deleting posts is disabled for everyone
      delete: deny(),
    },
  },
});
```

## when()

The `when()` function creates a conditional rule. It takes a condition function and allows the action only if the condition returns `true`.

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

when((context, action, resource) => boolean);
```

### Condition Function

The condition function receives:

| Parameter  | Type        | Description                  |
| ---------- | ----------- | ---------------------------- |
| `context`  | `TContext`  | Current user/request context |
| `action`   | `TAction`   | The action being performed   |
| `resource` | `TResource` | The resource being accessed  |

It must return a `boolean`:

- `true` → action is allowed
- `false` → action is denied

### Example: Owner-Only Access

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

const policy = createPolicy<AppContext>({
  resources,
  actions,
  rules: {
    post: {
      read: allow(),

      // Only the author can edit their post
      write: when((ctx, _, post) => ctx.user?.id === post.authorId),

      // Only the author can delete their post
      delete: when((ctx, _, post) => ctx.user?.id === post.authorId),
    },
  },
});
```

## Using the Action Parameter

The `action` parameter becomes useful when you create reusable condition functions that handle multiple actions differently. This lets you share logic across actions while still customizing behavior:

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

type PostAction = "read" | "write" | "delete";
type Post = { id: string; authorId: string; visibility: "public" | "private" };

// Reusable condition that behaves differently based on action
const canAccessPost: ConditionFn<AppContext, PostAction, Post> = (ctx, action, post) => {
  // Anyone can read public posts
  if (action === "read" && post.visibility === "public") {
    return true;
  }

  // For write/delete (or private reads), must be the author
  return ctx.user?.id === post.authorId;
};

const policy = createPolicy<AppContext>({
  resources,
  actions,
  rules: {
    post: {
      // Same function handles all three actions with different logic
      read: when(canAccessPost),
      write: when(canAccessPost),
      delete: when(canAccessPost),
    },
  },
});
```

This pattern is useful when actions share similar logic but need slight variations — you define the condition once and reuse it across multiple actions.

## Combining Rules

Conditions can be composed with combinators. See [Conditions](/permit/conditions) for details on `and()`, `or()`, `not()`, and `has()`.

## Best Practices

1. **Start restrictive** — Leave actions without a rule (or use `deny()`), then explicitly allow
2. **Keep conditions pure** — Don't perform side effects in condition functions
3. **Avoid async operations** — Conditions must be synchronous; fetch data before checking
4. **Use descriptive variable names** — `isOwner`, `isMember`, `hasAccess`
5. **Extract complex conditions** — Create reusable condition functions for clarity
