Policy Fundamentals
Verdict replaces scattered authorization checks in application code with a declarative set of rules in one place. A file containing these rules is called a policy and is written in the Verdict language (.vd).
Predicates
Section titled “Predicates”The core building block of a Verdict policy is the predicate:
// Examples of predicatesallow(user, action, resource)has_role(user, role, organization)is_owner(user, document)is_archived(document)Although a predicate looks like a function call in imperative languages, it represents a logical statement that is true or false depending on the terms inside.
Every predicate has a snake_case name followed by 1 to 3 terms in parentheses. Aside from allow, predicate names are up to you and use your application’s domain vocabulary.
Each slot inside a predicate holds a term:
// predicate(term1, term2, term3)// [name] [term] [term] [term]A term can be a concrete value or a variable:
- Identities (
TypeName:id) reference specific application entities such asUser:alice,Document:handbook, orOrganization:acme. The type name starts with an uppercase letter (PascalCase), and the ID after the colon is treated as a string key. Quote the ID if it contains characters like@or-(for example,User:"alice@example.com"). - Constants are primitive values: strings (
"read"), integers (32), or booleans (true,false). - Variables are lowercase
snake_casenames (user,document,org) that act as placeholders matching concrete values when Verdict evaluates a policy.
// has_role(User:alice, "member", org)// identity string variable
// min_clearance(Document:handbook, 2)// identity int
// is_public(Document:handbook, false)// identity booleanBecause an identity includes its type, User:alice and Organization:alice are distinct values and never match each other.
Facts and rules
Section titled “Facts and rules”Verdict separates your application’s changing authorization data (facts) from your access control logic (rules).
A fact is a predicate where every term is a concrete value (also called fully bound):
is_owner(User:alice, Document:handbook);has_role(User:alice, "member", Organization:acme);is_archived(Document:handbook);Facts represent relationships and attributes from your application, such as who owns a document or who belongs to an organization. You can write concrete facts directly in a .vd file for self-contained examples and tests, or supply live application facts at runtime through Verdict Server or Verdict Postgres.
Fact declarations
Section titled “Fact declarations”Write declare declarations to document the shapes of facts your policy expects:
declare is_owner(User, Document);declare has_role(User, string, Organization);declare is_archived(Document);Each position in a declare declaration names the expected term type: an identity type (User, Document, Organization) or a primitive type (string, int, boolean). Declaring your input facts lets verdict validate and editor tooling catch typos in predicate names and arity mismatches across your rules. See fact type declarations for the supported forms.
A rule defines when a predicate is true based on other predicates. It has a head (the conclusion before if) and a body (the conditions after if):
// If user is the owner of document, allow user to "read" documentallow(user, "read", document) if is_owner(user, document);Variables in a rule can include a type qualifier (variable: TypeName) to restrict which values they can match:
allow(user: User, "read", document: Document) if is_owner(user, document);
allow(user: User, "read", folder: Folder) if is_owner(user, folder);Notice how casing before the colon distinguishes a concrete identity from a qualified variable:
User:alice(uppercase on the left) is one specific identity: theUserwith ID"alice".user: User(lowercase on the left) is a variable nameduserthat matches any identity of typeUser.
Unlike an imperative chain of if statements that runs top to bottom, rules in Verdict are declarative and order-independent. Verdict searches across all rules in the policy and uses whichever rules match the question being asked.
How rules become authorization
Section titled “How rules become authorization”The entry point for authorization checks is the allow predicate:
allow(actor, action, resource)When your application calls authorize, it supplies concrete values for all three positions: the actor making the request, the action being performed, and the target resource.
Suppose your policy contains this rule:
declare is_owner(User, Document);
allow(user: User, "read", document: Document) if is_owner(user, document);When your application checks whether User:alice can "read" Document:handbook, Verdict evaluates allow(User:alice, "read", Document:handbook):
// 1. Match the query against the rule head:// user = User:alice, action = "read", document = Document:handbook
// 2. Substitute those values into the rule:allow(User:alice, "read", Document:handbook) if is_owner(User:alice, Document:handbook);
// 3. Check whether is_owner(User:alice, Document:handbook) is trueIf is_owner(User:alice, Document:handbook) is present, the rule’s condition is satisfied and Verdict returns true (allowed).
Authorization in Verdict is denied by default. If no rule can prove allow(actor, action, resource), the decision is false (denied).
Next steps
Section titled “Next steps”- Combine multiple conditions, define helper predicates, and verify your policy locally in Combining & Testing Rules.
- Explore the dedicated Language explanations and Designing Policies.
- Connect your backend in Integrate with Verdict Server.