Strapi Permissions — Per-Endpoint Permissions and CRUD Granularity
In this tutorial, you will learn how to configure granular permissions in Strapi for each content type, controller, and action, and how to implement custom permission logic using policies to control exactly who can do what.
What You'll Learn
- How Strapi's permission system maps roles to content type actions
- How to configure per-endpoint permissions in the admin panel
- How CRUD granularity works for each content type
- How to create custom policies for complex access rules
- How to restrict access based on user ownership or field values
- How permissions interact with API tokens
Why It Matters
Generic role-based access is not enough for real applications. A user should be able to edit their own profile but not others. An editor should be able to modify articles but not delete them. A moderator should be able to unpublish offensive comments. Granular permissions make this possible without custom code for simple cases and through policies for complex ones.
Real-World Use
A multi-author blog needs: Authors can create articles and edit only their own. Editors can edit any article but cannot delete. Admins have full control. Public users can only read published articles. The permission system handles all of this through per-role, per-endpoint configuration, and a custom "own author" policy.
Learning Path
flowchart LR A["Users & Roles"] --> B["Permissions
-- You are here"]:::current B --> C["Authentication"] C --> D["SSO & OAuth"] D --> E["Advanced Auth"] classDef current fill:#4945ff,color:#fff,stroke-width:2px
Permission Structure
Strapi permissions are organized by content type, controller, and action. Each role has a set of enabled or disabled permissions.
Permission hierarchy:
Role (Public, Authenticated, Editor, Admin)
-> Content Type (Article, Author, Category, Comment)
-> Controller (article, author, category, comment)
-> Action (find, findOne, create, update, delete)
When an API request arrives, Strapi checks:
- Does the request include a valid JWT or API token?
- Which role does the user have?
- Does that role have permission for the requested content type, controller, and action?
- If a policy is configured, does the policy pass?
Configuring Permissions in the Admin Panel
Permissions are configured per role in the admin panel:
Settings > Users & Permissions > Roles > Edit a Role
-- Permissions tab
-- Content Types section (all your content types)
-- Plugins section (plugin-specific permissions)
-- Settings section (admin settings)
Each content type lists its available actions:
Article
-- article controller:
[ ] find -- List all articles
[ ] findOne -- Get one article
[ ] create -- Create a new article
[ ] update -- Update an article
[ ] delete -- Delete an article
[ ] createLocalization -- Create translation
[ ] publish -- Publish an article (Strapi 5)
You enable or disable each checkbox per role. The configuration is saved to the database and applied immediately.
CRUD Granularity
Each content type has five standard CRUD actions plus optional actions depending on configuration:
// Standard CRUD permissions
// find — List entries (GET /api/articles)
// findOne — Get single entry (GET /api/articles/:id)
// create — Create entry (POST /api/articles)
// update — Update entry (PUT /api/articles/:id)
// delete — Delete entry (DELETE /api/articles/:id)
// Additional actions (Strapi 5)
// publish — Publish entry (POST /api/articles/:id/publish)
// unpublish — Unpublish entry (POST /api/articles/:id/unpublish)
// createLocalization — Create translated version
// Example permission matrix:
// Public Authenticated Editor Admin
// find Yes Yes Yes Yes
// findOne Yes Yes Yes Yes
// create No Yes Yes Yes
// update No No Yes Yes
// delete No No No Yes
// publish No No Yes Yes
Ownership-Based Permissions
Strapi does not have built-in ownership-based permissions (a user can only edit their own articles). You implement this with custom policies.
// src/policies/is-owner.js
module.exports = (policyContext, config, { strapi }) => {
// Get the authenticated user
const userId = policyContext.state.user?.id;
if (!userId) return false;
// Get the resource ID from the URL
const resourceId = policyContext.params.id;
if (!resourceId) return false;
// Get the content type from the route
// This policy works for any content type that has a "user" relation
const contentType = policyContext.route.info?.plugin
? policyContext.route.info.plugin
: `api::${policyContext.route.info.apiName}.${policyContext.route.info.apiName}`;
// Check if the resource belongs to the current user
const resource = await strapi.db.query(contentType).findOne({
where: { id: resourceId },
populate: ["user"],
});
return resource?.user?.id === userId;
};
Apply the policy to specific routes:
// src/api/article/routes/custom-article.js
module.exports = {
routes: [
{
method: "PUT",
path: "/articles/:id",
handler: "article.update",
config: {
policies: ["is-owner"], // Only owner can update
},
},
{
method: "DELETE",
path: "/articles/:id",
handler: "article.delete",
config: {
policies: ["is-owner"], // Only owner can delete
},
},
],
};
Field-Level Permissions
Strapi does not have built-in field-level permissions (a user can see the title but not the price). You implement this by customizing the controller response.
// src/api/article/controllers/article.js
const { createCoreController } = require("@strapi/strapi").factories;
module.exports = createCoreController("api::article.article", ({ strapi }) => ({
async find(ctx) {
const { data, meta } = await super.find(ctx);
// Filter fields based on user role
const user = ctx.state.user;
const filteredData = data.map((item) => {
const attrs = { ...item.attributes };
// Hide price field for non-admin users
if (!user || user.role?.name !== "Admin") {
delete attrs.price;
}
return { ...item, attributes: attrs };
});
return { data: filteredData, meta };
},
}));
API Token Permissions
API tokens bypass user roles. They have their own permission scope.
// Creating an API token:
// Settings > Administration Panel > API Tokens > Create Token
// Token configuration:
// Name: "Frontend Production Token"
// Description: "Used by the React frontend"
// Token type: Read-only (or Custom)
// Lifespan: Unlimited (or specific duration)
// Custom token permissions:
// Content Types:
// [x] Article: find, findOne
// [x] Category: find, findOne
// [ ] Article: create, update, delete
// [ ] User: any
API tokens are ideal for server-to-server communication where there is no logged-in user. The token's permissions are independent of any role.
Custom Permission Logic
For complex permission scenarios, create a custom policy:
// src/policies/can-review.js
module.exports = async (policyContext, config, { strapi }) => {
const user = policyContext.state.user;
if (!user) return false;
const role = user.role;
// Editors can review any article
if (role.name === "Editor") return true;
// Authors can review articles in their category
if (role.name === "Author") {
const articleId = policyContext.params.id;
const article = await strapi.db.query("api::article.article").findOne({
where: { id: articleId },
populate: ["category", "category.moderators"],
});
// Check if this author is a moderator of the article's category
return article?.category?.moderators?.some(
(mod) => mod.id === user.id
);
}
return false;
};
Policies are registered in config/policies.js or in the route configuration.
Permission Best Practices
Start restrictive, open gradually. Begin with no permissions for all roles, then enable only what each role needs. It is easier to add permissions than to fix over-permissive roles.
Use API tokens for frontend-server communication. Do not use user accounts for server-to-server API access. Create dedicated API tokens with minimal permissions.
Test permissions per role. Create a test account for each role and verify exactly what it can and cannot access. Automated tests help catch permission regressions.
Audit permissions regularly. Review role permissions monthly to ensure no excessive permissions have been added. Remove permissions that are no longer needed.
Document your permission matrix. Maintain a table showing which roles have access to which content type actions. Share this with your team to prevent confusion.
Common Mistakes
Enabling all permissions for Authenticated role. Giving authenticated users full CRUD on all content types is a security risk. Enable only the minimum permissions each role needs.
Not understanding that permissions apply to API endpoints, not admin panel. Role permissions control API access. Admin panel access is controlled separately through admin roles.
Using API tokens with excessive permissions. Creating API tokens with all permissions enabled defeats the purpose of access control. Use the principle of Least Privilege for tokens.
Forgetting that public users count as "Public" role. If you enable article:delete for the Public role, anyone on the internet can delete your content.
Not testing permission changes. Changing a role's permissions can break your frontend when users start getting 403 errors. Always test permission changes in a development environment.
Practice Questions
What are the five standard CRUD actions for Strapi permissions? Answer: find (list), findOne (single), create, update, delete. Each can be enabled or disabled per role per content type.
How do you implement ownership-based permissions in Strapi? Answer: By creating a custom policy that checks if the authenticated user's ID matches the resource's owner field (e.g.,
resource.user.id === currentUser.id).What is the difference between role permissions and API token permissions? Answer: Role permissions apply to authenticated users. API token permissions are independent of user roles and are used for server-to-server access. Tokens can have different scopes than user roles.
Challenge: Design and implement a complete permission system for a collaborative content platform: (1) Create roles for Viewer (read only), Contributor (create, edit own), Reviewer (read all, edit none), Publisher (read, edit, publish all), Admin (full CRUD on everything). (2) Implement a custom policy that lets Contributors edit only articles where they are listed as co-authors. (3) Create API tokens for external services with read-only access. (4) Write tests that verify each role's permissions.
FAQ
Mini Project
Your task: Build a complete permission system for a documentation platform.
- Create content types: Document (title, content, visibility: public/internal/confidential, author), Category (name, access_level: public/internal).
- Create roles: Reader (public), Team Member (authenticated), Manager (custom), Admin (custom).
- Configure permissions:
- Reader: find, findOne on Documents where visibility=public
- Team Member: find, findOne on all Documents, create Documents
- Manager: full CRUD on Documents, manage Categories
- Admin: full access to everything
- Implement a custom policy that restricts Document visibility based on the user's role and the document's visibility field.
- Create test users, add sample data, and verify permission enforcement through the API.
What's Next
Now that you understand permissions, proceed to Authentication to learn about JWT tokens, login and register flows, and token refresh mechanisms. After that, explore SSO & OAuth for social login integration.
Related lessons:
- REST API Security — Authentication and authorization
- Node.js JWT — How JWT tokens work
- WordPress User Permissions — Compare permission systems
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro