---
title: "How permissions work"
slug: features/permissions-model
url: https://projectri.com/docs/features/permissions-model
section: features
audience: everyone
mobile: false
updated: 2026-09-08
source: Projectri documentation
---

# How permissions work

The three tiers Projectri decides "may this person do this" with, how a role default becomes one person's answer, and the two traps that catch every new key.

![How permissions work](https://projectri.com/docs-shots/admin-permissions.png)

## What it is

Every screen and every API call in Projectri checks a named **permission key**,
like `task.create` or `finance.invoice.view` — never a role. A role is only a
bundle of keys, and the bundle is data an admin can change. That is why "can Sam
do this" has a real answer rather than a guess.

There are three tiers, each narrower than the last, plus one question about the
row itself. All four have to say yes.

## How to get there

The keys themselves are visible at **Admin → Permissions**, as a grid of roles
against keys with a per-person override table beside it. Reading that screen
needs `security.view`; changing it is an admin act. **Admin → Roles** shows the
four roles.

You do not need a permission to *be* affected by one. Everywhere else in the
product, a key you do not hold shows up as a control that is simply not offered
— the row is absent from the navigation, the button is not drawn — and the route
behind it answers **403** if you reach it another way.

## How to use it

### Read what a key means

1. Go to **Admin → Permissions**.
2. Keys are named `<resource>.<verb>` and grouped by area. `.view` is always
   read-only; every other verb changes something and writes an audit entry.
3. Two wildcards exist in a granted set: `*` for everything and `project.*` for
   a whole resource. ADMIN holds `*`, which is why a key added tomorrow reaches
   admins with no migration.

### Give one person a key without changing their role

1. **Admin → Permissions** → the override table beside the grid.
2. Find the person, switch the key on or off.
3. It takes effect on their **next request**. A per-person override beats the
   company's role setting, which beats the platform-wide role default.

### Change what a role can do for this workspace only

1. **Admin → Permissions** → the grid.
2. Toggle the key for the role. This is a delta on top of the shipped default;
   other workspaces are unaffected.
3. An explicit "off" removes the key even when the layer below granted it, so
   later and more specific always wins.

### Cap what someone can do inside one project

1. Open the project → its member list.
2. Give them a project role. It **caps** what the workspace already allowed and
   can never grant anything.

| Project role | Keeps |
|---|---|
| ADMIN | everything the workspace tier allowed |
| EDITOR | all but `project.delete`, `project.archive`, `project.member_manage`, `project.budget`, `sprint.delete` |
| COMMENTER | `.view` keys, plus `task.comment`, `document.comment`, `discussion.post`, `time.log` |
| VIEWER | `.view` keys only |

Being absent from the member list is **not** a restriction. Most projects have
no explicit member list at all, and treating "not listed" as "locked out" would
close a workspace out of its own data. The list is an opt-in narrowing on the
people named in it.

## What it affects

- **The navigation rail, the command palette and the keyboard shortcuts read
  the same keys.** `⌘2` for My Tasks falls through unhandled if you do not hold
  `task.view`, so the three can never disagree about what is reachable.
- **API tokens and connected apps are narrowed by it.** A token's real access is
  its stored scopes **intersected with the live permissions of the person who
  created it**. Demote that person and the token narrows on its next call;
  suspend them and it dies. Nobody has to remember to revoke it.
- **Nia is bound by it.** Her tools re-check the key rather than trusting that
  she is trusted, so she is never a way round a gate.
- **Suspension is total.** A suspended or removed member resolves to *no keys at
  all*, not to GUEST — GUEST still carries a few reads.
- **Support tickets are the one place a `.view` key is not the whole answer.**
  `support.view` gets you a queue; which tickets are in it depends on being the
  reporter, the assignee, on the assigned team, a named participant, or on the
  ticket's project. `support.manage` sees the whole queue.

## On mobile

The phone reads the same resolved key list and hides the same controls. There is
no permission editor in the app — the grid is workspace configuration done once
at a desk, and the phone shows the result of it everywhere it applies.

## Limits and gotchas

> [!WARNING]
> **Every new key ending in `.view` reaches VIEWER by default.** The VIEWER role
> is derived as "every `.view` key, minus two exclusion lists", so a key added
> for a sensitive screen reaches every viewer — including a guest on a client
> login — unless it was deliberately excluded. If you are adding a key, this is
> the decision to get right.

- **A grant is inert until the RBAC seed has run.** The key set lives in code
  and is reconciled into the database by `scripts/seed-org-rbac.mjs`. Until that
  runs, a newly added key grants nothing. The seed also *revokes*: it is a
  reconciliation, not an append. It never touches an admin's own edits.
- **Editing a row can need two permissions, not one.** `task.update` means "edit
  anybody's task"; `task.update_own` means "edit the ones you created, are
  assigned, are reviewing or are a co-assignee on". Holding the second without
  the first gives you a box you can type into only on your own work — and the
  project tier is re-applied on the way through either way.
- **"Edit" is split further on a task.** Changing the assignee or reviewer needs
  `task.assign`; moving it in or out of a sprint needs `sprint.scope`. Fixing a
  typo on your own task does not let you push it onto a colleague.
- **The client-side check is optimistic.** `can()` answers `true` before the
  server has said otherwise, so a control can appear for a moment and then go.
  The server refuses either way; nothing you see for 200ms was ever permitted.
- **`role === ADMIN` is not cross-tenant.** Being an admin of one workspace
  confers nothing in another; the role lives on the membership row, so one
  person can be ADMIN in one workspace and VIEWER in the next.
- **A member starts on the floor.** New members get what they need for their own
  work. The client list, contracts, reports, proofs, the knowledge base, forms
  and playbooks are all grants — one toggle each, and the row appears in that
  person's rail on their next request.

## Related

- [Roles and permissions](/docs/start/roles-and-permissions) — the four roles and what each is for.
- [Permissions](/docs/admin/permissions) — the grid and the override table.
- [Plans and entitlements](/docs/features/plans-and-entitlements) — the other reason a screen refuses you, and how to tell them apart.
- [Nia, the assistant](/docs/features/nia-assistant) — why she cannot exceed your own access.

## Related

- [Roles and permissions](https://projectri.com/docs/start/roles-and-permissions.md): The four workspace roles, the permission keys behind them, and why two people in the same workspace see different screens.
- [Permissions](https://projectri.com/docs/admin/permissions.md): The role-by-permission grid and the per-member override table — what each role may do in this workspace, and how one person differs from their role.
- [Roles](https://projectri.com/docs/admin/roles.md): The four company roles, how many people hold each, how far this workspace has moved from the platform default, and how to put a role back.
- [Plans and entitlements](https://projectri.com/docs/features/plans-and-entitlements.md): What each plan includes, what happens when you hit a limit, and why losing a feature never loses the data behind it.
