---
id: create-permission-model
title: How to create a permission model with Ory Permission Language
sidebar_label: Create a permission model
---

# Create a permission model

```mdx-code-block
import Mermaid from "@site/src/theme/Mermaid"
```

This section guides you through creating your first permission model using the Ory Permission Language (OPL).

## What is a permission model?

A permission model is a set of rules that define which relations are checked in the database during a permission check.

Permission checks are answered based on:

- The data available in the database, for example: `User:Bob is owner of Document:X`
- Permission rules, for example: "All owners of a document can view it".

When you ask Ory Permissions: `is User:Bob allowed to view on Document:X`, the system checks up how Bob could have the `view`
permission, and then checks if Bob is owner of the document X. The permission model tells Ory Permissions what to check in the
database.

## How to define a permission model

Designing a permission model is a complex task. Ory Permissions and the Ory Permission Language, as a subset of TypeScript,
provide a streamlined approach to permissions with the benefit of being processed by a fast, global permission engine.

Just as there is no single approach for programming, there is no universally applicable guide for constructing a permission model.
Nevertheless, the following iterative process can be a good starting point:

1. Create a list of objects. Objects are the entities that you want to manage access for.
2. Make a list of relationships each object has to other objects. In a database, those would be associations expressed with
   foreign keys.
3. Define each relation in the OPL.
4. Make a list of permissions that you want to check.
5. Define each permission in the OPL.
6. Test your permission model.

## Example: document store

To guide you through the process of defining a permission model, this example will be used:

- A user can be the owner, editor, or viewer of a document.
- The owner of a document is also an editor of that document.
- An editor of a document is also a viewer of that document.
- Documents can be arranged into a hierarchy of folders.
- Users that can view the parent folder in the hierarchy can also view all folders and documents the parent folder contains.

### Create a list of objects and subjects

In the first step, look at the initial list of assumptions and identify all `objects` and `subjects`:

- A `user` can be the owner, editor, or viewer of a `document`.
- The owner of a `document` is also an editor of that `document`.
- An editor of a `document` is also a viewer of that `document`.
- `Documents` can be arranged into a hierarchy of `folders`.
- `Users` that can view the parent `folder` in the hierarchy can also view all `folders` and `documents` the parent `folder`
  contains.

After highlighting, you can identify the subject, `User`, and two objects: `Document` and `Folder`.

This list translates into the first version of the permission model.

```ts title="permissions-v1.ts"
import { Namespace, Context } from "@ory/keto-namespace-types"

// highlight-start
class User implements Namespace {}
class Document implements Namespace {}
class Folder implements Namespace {}
// highlight-end
```

:::tip

In Ory Permissions, objects and subjects you identified are [namespaces](../concepts/05_namespaces.mdx). They are declared as
classes in the Ory Permission Language. The convention is to name these classes like TypeScript classes, starting with a capital
letter and and in the singular form, for example `Document` instead of `documents`.

:::

### Determine relationships between objects

Read through the initial assumptions point by point to determine the list of relationships:

- The goal is to model `user` access to `documents` such that `users` can be the `owner`, `editor`, or `viewer` of a `document`.

```mdx-code-block
<Mermaid
  chart={`
graph LR
    Document -->|owner| User
    Document -->|editor| User
    Document -->|viewer| User
`}
/>
```

- Every owner is also an editor of a `document` and every editor is also a viewer of a `document`. Furthermore, `documents` can be
  put into a hierarchy of `folders`. If a user can view the parent `folder`, they can also view all `folders` and `documents` the
  parent `folder` contains.

```mdx-code-block
<Mermaid
  chart={`
graph LR
    Document -->|parent| Folder
    Folder -->|parent| Folder
    Folder -->|owner| User
    Folder -->|editor| User
    Folder -->|viewer| User
`}
/>
```

This is the complete matrix of relationships presented on a single diagram:

```mdx-code-block
<Mermaid
  chart={`
graph LR
    Document -->|owner| User
    Document -->|editor| User
    Document -->|viewer| User
    Document -->|parent| Folder
    Folder -->|parent| Folder
    Folder -->|owner| User
    Folder -->|editor| User
    Folder -->|viewer| User
`}
/>
```

### Define relationships in the OPL

Next, add each relation to the permission config.

:::tip

In Ory Permissions, relationships are declared inside the corresponding class in the Ory Permission Language. Ory Permissions only
has [many-to-many relationships](<https://en.wikipedia.org/wiki/Many-to-many_(data_model)>) between objects and subjects. To
reflect this in OPL, pluralize the relation name, for example, `viewers` instead of `viewer`.

:::

```ts title="permissions-v2.ts"
import { Namespace, Context } from "@ory/keto-namespace-types"

class User implements Namespace {}

class Document implements Namespace {
  // highlight-start
  related: {
    owners: User[]
    editors: User[]
    viewers: User[]
    parents: Folder[]
  }
  // highlight-end
}

class Folder implements Namespace {
  // highlight-start
  related: {
    owners: User[]
    editors: User[]
    viewers: User[]
    parents: Folder[]
  }
  // highlight-end
}
```

### List permissions to check for each object

When you perform an action on behalf of a user, you should check for a specific permission, such as `view` or `edit`, instead of a
relationship, such as `owner`. The concrete permission is then still checked against the relationships. For example, if `owners`
can `view` a file and the `view` permission is checked, then the `owners` relation is looked up in the relationships database.

Add these permissions to the model to express what the application needs. For our document storage, you have these permissions:

- `view` a document if the user is a `viewer`, `editor`, or `owner` of the document; or if the user can `view` the parent folder
- `edit` a document if the user is a `editor`, or `owner` of the document; or if the user can `edit` the parent folder
- `delete` a document if the user is an `owner` of the document; or if the user can `delete` the parent folder
- `share` a document if the user is an `owner` of the document; or if the user can `share` the parent folder
- `delete` a folder if the user is an `owner` of the folder; or if the user can `delete` the parent folder
- `share` a folder if the user is an `owner` of the folder; or if the user can `share` the parent folder

### Define permissions in the OPL

The permissions are expressed in the OPL as TypeScript functions that take a context containing the subject and that answer
permission checks based on the relationships the object has to the subject.

:::tip

The permissions from the description are declared as functions inside the `permits` property of the corresponding class in the
OPL.

:::

Let's see how the permissions translate into code:

- `view` a document if the user is a `viewer`, `editor`, or `owner` of the document or if the user can `view` the parent folder

  ```ts title="permissions-v3.ts"
  import { Namespace, Context } from "@ory/keto-namespace-types"

  class User implements Namespace {}

  class Document implements Namespace {
    related: {
      owners: User[]
      editors: User[]
      viewers: User[]
      parents: Folder[]
    }

    permits = {
      //highlight-start
      view: (ctx: Context): boolean =>
        this.related.viewers.includes(ctx.subject) ||
        this.related.editors.includes(ctx.subject) ||
        this.related.owners.includes(ctx.subject) ||
        this.related.parents.traverse((parent) => parent.permits.view(ctx)),
      //highlight-end
    }
  }

  class Folder implements Namespace {
    related: {
      owners: User[]
      editors: User[]
      viewers: User[]
      parents: Folder[]
    }
  }
  ```

- Code for the remaining permissions:

  ```ts title="permissions-v4.ts"
  import { Namespace, Context } from "@ory/keto-namespace-types"

  class User implements Namespace {}

  class Document implements Namespace {
    related: {
      owners: User[]
      editors: User[]
      viewers: User[]
      parents: Folder[]
    }

    permits = {
      view: (ctx: Context): boolean =>
        this.related.viewers.includes(ctx.subject) ||
        this.related.editors.includes(ctx.subject) ||
        this.related.owners.includes(ctx.subject) ||
        this.related.parents.traverse((parent) => parent.permits.view(ctx)),
      //highlight-start
      edit: (ctx: Context): boolean =>
        this.related.editors.includes(ctx.subject) ||
        this.related.owners.includes(ctx.subject) ||
        this.related.parents.traverse((parent) => parent.permits.edit(ctx)),
      delete: (ctx: Context): boolean =>
        this.related.owners.includes(ctx.subject) || this.related.parents.traverse((parent) => parent.permits.delete(ctx)),
      share: (ctx: Context): boolean =>
        this.related.owners.includes(ctx.subject) || this.related.parents.traverse((parent) => parent.permits.share(ctx)),
      //highlight-end
    }
  }

  class Folder implements Namespace {
    related: {
      owners: User[]
      editors: User[]
      viewers: User[]
      parents: Folder[]
    }

    //highlight-start
    permits = {
      delete: (ctx: Context): boolean =>
        this.related.owners.includes(ctx.subject) || this.related.parents.traverse((parent) => parent.permits.delete(ctx)),
      share: (ctx: Context): boolean =>
        this.related.owners.includes(ctx.subject) || this.related.parents.traverse((parent) => parent.permits.share(ctx)),
    }
    //highlight-end
  }
  ```

#### Optional: refactoring

Notice that the permission model you created is hierarchical. This means that everybody who can `view` can also `edit` documents,
and everybody that can `edit` can also `delete` and `share`.

You can refactor the `view` permission like this:

```diff
  view: (ctx: Context): boolean =>
    this.related.viewers.includes(ctx.subject) ||
-   this.related.editors.includes(ctx.subject) ||
-   this.related.owners.includes(ctx.subject) ||
-   this.related.parents.traverse((parent) => parent.permits.view(ctx))
+   this.permits.edit(ctx)
```

When you apply this to the entire config, you get this code:

```ts title="permissions-v5.ts"
import { Namespace, Context } from "@ory/keto-namespace-types"

class User implements Namespace {}

class Document implements Namespace {
  related: {
    owners: User[]
    editors: User[]
    viewers: User[]
    parents: Folder[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.includes(ctx.subject) ||
      //highlight-next-line
      this.permits.edit(ctx),
    edit: (ctx: Context): boolean =>
      this.related.editors.includes(ctx.subject) ||
      //highlight-next-line
      this.permits.share(ctx),
    delete: (ctx: Context): boolean =>
      //highlight-next-line
      this.permits.share(ctx),
    share: (ctx: Context): boolean =>
      this.related.owners.includes(ctx.subject) || this.related.parents.traverse((parent) => parent.permits.share(ctx)),
  }
}

class Folder implements Namespace {
  related: {
    owners: User[]
    editors: User[]
    viewers: User[]
    parents: Folder[]
  }

  permits = {
    delete: (ctx: Context): boolean =>
      //highlight-next-line
      this.permits.share(ctx),
    share: (ctx: Context): boolean =>
      this.related.owners.includes(ctx.subject) || this.related.parents.traverse((parent) => parent.permits.share(ctx)),
  }
}
```

Whether or not this refactoring makes sense in your application depends on your requirements, of course. This kind of refactoring
is possible because the permission config is essentially just code. This means that you can apply the same techniques to your
permission config as you apply to your code in order to create well-structured and easily-maintainable software systems.

### Test permissions

It's important to test your permission model. To test the permissions manually, you can
[create relationships and check permissions](../guides/simple-access-check-guide.mdx) through the API or SDK.

For continuous testing, we recommend the following best practices:

- Automate testing your permission model. Write a test that inserts the relationships and checks the permissions through the
  [SDK](../sdk/01_overview.md). Use your preferred programming language.
- For complex permission model changes, use a separate Ory Network project. Each Ory Network project has an isolated permission
  model, so you can iterate on and test your changes on a test project and deploy the changes only when all tests pass.
