Skip to content

Strapi Permissions — Per-Endpoint Permissions and CRUD Granularity

DodaTech Updated 2026-06-28 9 min read

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:

  1. Does the request include a valid JWT or API token?
  2. Which role does the user have?
  3. Does that role have permission for the requested content type, controller, and action?
  4. 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

  1. 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.

  2. 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.

  3. 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.

  4. Audit permissions regularly. Review role permissions monthly to ensure no excessive permissions have been added. Remove permissions that are no longer needed.

  5. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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

  1. 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.

  2. 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).

  3. 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.

  4. 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

Can I set permissions per field instead of per endpoint?

Strapi does not have built-in field-level permissions. You must implement field filtering in custom controllers or services. The private field setting can exclude fields from API responses globally.

How do permissions interact when a user has an API token?

API tokens have their own permission scope that is independent of the user's role. The token's permissions are checked separately from any user role permissions.

Can I create hierarchical permissions like 'Editor inherits all Author permissions'?

No, Strapi permissions are not hierarchical. Each role has its own set of enabled permissions. You must configure each role independently.

How do I restrict access to unpublished content?

By default, the API returns only published content. The Public role cannot see drafts. Use publicationState=preview with appropriate permissions to allow specific roles to preview drafts.

What happens if I delete a content type that has permissions configured?

Strapi removes the associated permission entries from the database when you delete a content type. Roles will no longer show that content type in their permission configuration.

Mini Project

Your task: Build a complete permission system for a documentation platform.

  1. Create content types: Document (title, content, visibility: public/internal/confidential, author), Category (name, access_level: public/internal).
  2. Create roles: Reader (public), Team Member (authenticated), Manager (custom), Admin (custom).
  3. 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
  4. Implement a custom policy that restricts Document visibility based on the user's role and the document's visibility field.
  5. 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:

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro