Directus's permission system is genuinely powerful: role-based, with collection-level, item-level, and field-level granularity. That power is also exactly how teams end up with the wrong permissions in production. It's easy to grant broad access during a sprint and forget to narrow it before launch. Here's how to set it up so that doesn't happen.

Start every role at zero

The default instinct is to duplicate an existing permissive role and remove a few things. Don't. Create new roles with nothing granted, then add permissions one collection and one field at a time, asking "does this specific role need this specific access" every time. It's slower during development and it's the only way to end up with a permission set you can actually reason about later.

Use item-level rules for ownership

Collection-level permission answers "can this role touch this collection at all." Item-level permission rules (Directus's filter-based access) answer the harder question: "can this specific user touch this specific row." A customer-support role reading a support_tickets collection usually shouldn't see every ticket. It should see tickets where assigned_to == $CURRENT_USER.id, enforced by a filter on the permission itself, not by trusting the front end to only ask for the right rows.

 KryeoDefault-allow approach
Starting pointEvery role starts with zero permissionsDuplicate an existing role, remove a few things
Public roleAudited explicitly before launch, field by fieldInherited from development convenience settings
Row-level accessItem-level filter rules (e.g. assigned_to = current user)Enforced only in front-end query logic
Public writesRouted through a validated Flow or server endpointRaw create permission on the public role
Pattern comparison, not a specific product claim.

Field-level rules for sensitive data

Even inside a collection a role can legitimately access, some fields need a tighter rule than the rest. Internal notes, cost prices, unmasked PII: use Directus's field permission overrides rather than splitting the collection into two just to hide a handful of fields. This keeps your content model clean while still enforcing the actual security boundary.

3
Permission layers Directus gives you: collection, item (row-level filter), and field. Most leaks happen from skipping the second two

Audit before every launch, not just the first one

Permissions drift. A role gets a temporary grant for a one-off migration script and nobody revokes it after. The fix is a checklist step, not a tool: before any production deploy that touches roles or permissions, walk every role (starting with Public) and confirm every granted permission is still intentional.

Inheriting a Directus instance with unclear permissions?

We'll audit every role against your actual data model and tell you exactly what's over-exposed before it becomes an incident.

Request an audit →

The rule

Default-deny, audit the Public role like it's the only one that matters (because for unauthenticated traffic, it is), and use item- and field-level rules instead of reaching for a new collection every time you need a narrower boundary. For the broader content-modeling mistakes this connects to, see Directus content model mistakes. We set this up as a standard part of every Directus development engagement.