Conditions
Build complex authorization rules using and(), or(), not(), and has() condition combinators in @zap-studio/permit.
Conditions are the building blocks for creating complex authorization rules. @zap-studio/permit provides combinators to compose simple conditions into powerful, readable policies.
Understanding Conditions
A condition is a function that evaluates to true or false:
type ConditionFn<TContext, TAction, TResource> = (
context: TContext,
action: TAction,
resource: TResource
) => boolean;Conditions are used with when() to create policy rules:
when((ctx, action, resource) => /* condition logic */)Condition Combinators
and()
The and() combinator creates a condition that passes only when all conditions are true. It short-circuits on the first false value.
import { and } from "@zap-studio/permit";
and(...conditions)Example: Multiple Requirements
import { createPolicy, when, and } from "@zap-studio/permit";
// User must be authenticated AND be the owner AND post must be published
const canEditPublishedPost = and(
(ctx) => ctx.user !== null,
(ctx, _, post) => ctx.user?.id === post.authorId,
(_, __, post) => post.status === "published"
);
const policy = createPolicy<AppContext>({
resources,
actions,
rules: {
post: {
edit: when(canEditPublishedPost),
},
},
});or()
The or() combinator creates a condition that passes when any condition is true. It short-circuits on the first true value.
import { or } from "@zap-studio/permit";
or(...conditions)Example: Multiple Access Paths
import { createPolicy, when, or } from "@zap-studio/permit";
// User can access if they're the owner OR an admin OR explicitly shared
const canAccess = or(
(ctx, _, doc) => ctx.user?.id === doc.ownerId,
(ctx) => ctx.user?.role === "admin",
(ctx, _, doc) => doc.sharedWith.includes(ctx.user?.id ?? "")
);
const policy = createPolicy<AppContext>({
resources,
actions,
rules: {
document: {
read: when(canAccess),
},
},
});not()
The not() combinator negates a condition.
import { not } from "@zap-studio/permit";
not(condition)Example: Exclusion Rules
import { createPolicy, when, not, and } from "@zap-studio/permit";
// Cannot interact with your own content
const isNotOwner = not((ctx, _, resource) => ctx.user?.id === resource.authorId);
// Content is not archived
const isNotArchived = not((_, __, resource) => resource.status === "archived");
const policy = createPolicy<AppContext>({
resources,
actions,
rules: {
post: {
// Can like any post except your own
like: when(isNotOwner),
// Can comment on posts that aren't archived
comment: when(isNotArchived),
// Can report posts that: you don't own AND aren't already reported by you
report: when(
and(
isNotOwner,
(ctx, _, post) => !post.reportedBy.includes(ctx.user?.id ?? "")
)
),
},
},
});has()
The has() helper creates a condition that checks if a context property equals a specific value.
import { has } from "@zap-studio/permit";
has(key, value)Example: Simple Property Checks
import { createPolicy, when, has, or } from "@zap-studio/permit";
type AppContext = {
role: "guest" | "user" | "admin";
isVerified: boolean;
plan: "free" | "pro" | "enterprise";
};
const policy = createPolicy<AppContext>({
resources,
actions,
rules: {
settings: {
// Only admins can access settings
read: when(has("role", "admin")),
},
billing: {
// Only verified users can access billing
read: when(has("isVerified", true)),
},
export: {
// Pro or Enterprise users can export
use: when(or(has("plan", "pro"), has("plan", "enterprise"))),
},
},
});Combining Combinators
Combinators can be nested to create complex conditions:
import { createPolicy, when, and, or, not } from "@zap-studio/permit";
const policy = createPolicy<AppContext>({
resources,
actions,
rules: {
document: {
// Can edit if:
// (owner OR admin) AND (not archived) AND (not locked OR is admin)
edit: when(
and(
or(
(ctx, _, doc) => ctx.user?.id === doc.ownerId,
(ctx) => ctx.user?.role === "admin"
),
not((_, __, doc) => doc.status === "archived"),
or(
not((_, __, doc) => doc.isLocked),
(ctx) => ctx.user?.role === "admin"
)
)
),
},
},
});Extracting Reusable Conditions
For cleaner code, extract conditions into named functions:
import { and, or, not } from "@zap-studio/permit";
import type { ConditionFn } from "@zap-studio/permit/types";
type DocContext = {
user: { id: string; role: string } | null;
};
type Document = {
ownerId: string;
status: string;
isLocked: boolean;
};
// Reusable conditions
const isOwner: ConditionFn<DocContext, string, Document> = (ctx, _, doc) =>
ctx.user?.id === doc.ownerId;
const isAdmin: ConditionFn<DocContext, string, Document> = (ctx) =>
ctx.user?.role === "admin";
const isArchived: ConditionFn<DocContext, string, Document> = (_, __, doc) =>
doc.status === "archived";
const isLocked: ConditionFn<DocContext, string, Document> = (_, __, doc) =>
doc.isLocked;
// Composed conditions
const canEdit = and(
or(isOwner, isAdmin),
not(isArchived),
or(not(isLocked), isAdmin)
);
// Use in policy
const policy = createPolicy<DocContext>({
resources,
actions,
rules: {
document: {
edit: when(canEdit),
},
},
});Best Practices
- Name your conditions — Extract complex logic into well-named functions
- Keep conditions simple — Each condition should check one thing
- Use short-circuit evaluation — Put fast/common checks first in
and()/or() - Avoid side effects — Conditions should be pure functions
- Document complex logic — Add comments explaining business rules
Last updated on