---
title: "Check permissions instead of role names · Agent Wranglers"
url: https://agentwranglers.se/playbook/accounts/roles-and-permissions/permissions-not-roles
updated: 2026-10-20
licence: "All rights reserved"
---

# Check permissions instead of role names

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Roles and permissions](https://agentwranglers.se/playbook/accounts/roles-and-permissions)
- Rule: Make code ask whether a user has a named permission, such as `invoice.refund`. Map roles to permissions in one place, and never compare role names in feature code.
- How: Write one list of permissions, each an action on a kind of record. Write one map from each role to its permissions, in code or in a table. Feature code calls one function, such as `can(membership, 'invoice.refund')`. A new or changed role is then one edit. An unknown role or permission gets no, as [deny by default](https://agentwranglers.se/playbook/adversarial-testing/access-and-accounts/deny-by-default) says. The interface may use the same function to hide buttons, but the server check is what counts.
- Check: Search the code for role comparisons, such as `role === 'admin'` or `role == "admin"`. Matches appear only in the policy module. A test confirms `can` answers no for an unknown role.

Example:

```js
const ROLE_PERMISSIONS = {
  owner: ['invoice.read', 'invoice.create', 'invoice.refund', 'member.manage', 'billing.manage'],
  admin: ['invoice.read', 'invoice.create', 'invoice.refund', 'member.manage'],
  member: ['invoice.read', 'invoice.create'],
  viewer: ['invoice.read'],
};

export const can = (membership, permission) =>
  ROLE_PERMISSIONS[membership?.role]?.includes(permission) ?? false;
```

```python
ROLE_PERMISSIONS = {
    "owner": {"invoice.read", "invoice.create", "invoice.refund", "member.manage", "billing.manage"},
    "admin": {"invoice.read", "invoice.create", "invoice.refund", "member.manage"},
    "member": {"invoice.read", "invoice.create"},
    "viewer": {"invoice.read"},
}

def can(membership, permission: str) -> bool:
    return membership is not None and permission in ROLE_PERMISSIONS.get(membership.role, set())
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers accounts playbook: https://agentwranglers.se/playbook/accounts/roles-and-permissions/permissions-not-roles`.

- Next: [Start with roles and add record rules only when needed](https://agentwranglers.se/playbook/accounts/roles-and-permissions/rbac-then-abac)
- [Roles and permissions](https://agentwranglers.se/playbook/accounts/roles-and-permissions)
