RBAC Concepts

EnterpriseBeta

In this guide, you'll learn about the main concepts of the role-based access control (RBAC) Module: policies, roles, and role assignments.

Policy#

A policy, represented by the RbacPolicy data model, is the smallest unit of access in RBAC. It allows an operation on a resource.

A policy has a key in the format resource:operation, where:

  • resource is the entity the policy applies to, such as product or order.
  • operation is the action allowed on the resource, such as read, create, update, or delete.

For example, the order:read policy allows viewing orders.

Granular Policies#

Since policies are defined at the resource-operation level, you can create granular access control by granting access to parent resources, but not to their children.

For example, the product:read policy allows viewing products, but not their variants. To allow viewing variants, you would also need to grant the product_variant:read policy.

Wildcard Policies#

A policy can use the wildcard * for its resource, operation, or both:

  • product:* allows all operations on products.
  • *:read allows reading all resources.
  • *:* allows all operations on all resources. This is the policy granted to the Super Admin role.

Core Policies#

Medusa defines a policy for every operation on each of its own commerce resources, such as products and orders. Every time the application starts, the RBAC Module syncs those core policies to the database, matching them by their key. The sync does the following:

  • Creates a policy that doesn't exist in the database yet.
  • Updates a policy whose name or description changed.
  • Restores a policy that was previously deleted.

The sync never deletes a policy, and it never changes a policy that you created yourself.

Note: You can't delete a core policy from the Medusa Admin dashboard, since policies are read-only there. If you delete one in your customizations, with the Delete Policy API route or the deleteRbacPoliciesWorkflow, the next restart restores it.
Warning: The notification:read policy allows a user to view the data property of every notification, which holds the data that your customizations and Medusa pass to notification templates. Only assign it to roles whose users can see that data. Learn more in the Send Notification guide.

You can also create policies for your custom resources. Learn how in the Define Custom Policies guide.


Role#

A role, represented by the RbacRole data model, groups a set of policies under a name, such as "Product Manager" or "Order Manager". You assign roles to admin users (or any actor type) to control what they can access.

A role has the following main properties:

  • name: The role's unique name.
  • description: An optional description of the role's purpose.
  • policies: The policies granted by the role.

An admin user assigned a role gains all the policies of that role.

Super Admin Role#

When you install the RBAC Module, the Medusa application does the following:

  1. Creates a Super Admin role with the *:* wildcard policy. This role has full access to all resources and operations. The RBAC Module creates it every time the application starts or you run database migrations, if it doesn't exist.
  2. Grants the Super Admin role, without a scope, to all existing admin users. This ensures your current users keep their full access after installing the RBAC Module. This happens when you run database migrations after installing the module.

New users created with the user CLI command are also assigned the Super Admin role by default while the RBAC Module is installed.

Medusa's workflows and API routes also protect the Super Admin role:

  • You can't delete the Super Admin role or its *:* policy.
  • You can't remove the Super Admin role from the last user that holds it without a scope, or delete that user.

Calling the RBAC Module's service directly skips these checks.

Some operations affect the whole application, so they require the *:* wildcard policy that the Super Admin role has:

  • Running a workflow with the Execute Workflow API route (POST /admin/workflows-executions/:workflow_id/run).
  • Creating, updating, or deleting a system default view configuration, and setting the default layout configuration, in the Medusa Admin.
Warning: The Succeed a Step and Fail a Step API routes (POST /admin/workflows-executions/:workflow_id/steps/success and /steps/failure) only require the workflow_execution:read and workflow_execution:update policies. A user with those policies can resume any long-running workflow waiting on an async step, even if they don't have the policies for the actions that the workflow performs. Only assign the workflow_execution:update policy to roles you trust with those actions.

Delete a Role#

When you delete a role, Medusa also deletes its links to policies and its role assignments. So, the entities assigned the role lose its policies.


Role Assignment#

A role assignment, represented by the RbacRoleAssignment data model, grants a role to an entity. Each assignment is a single record with the following main properties:

  • role_id: The ID of the granted role.
  • reference: The type of entity the role is granted to, such as user or invite.
  • reference_id: The ID of that entity, such as a user ID or invite ID.
  • scope and scope_id: An optional constraint on where the role applies.

Since the assignment stores the entity's type and ID rather than pointing to a specific data model, you can grant roles to admin users, pending invites, or your own custom entities.

An entity can have multiple roles. Its effective policies are the union of the policies from all its assignments that apply to the request.

An entity can only have one assignment of a role without a scope, and one assignment of a role per scope. Creating a duplicate assignment fails.

Assign a Role#

To assign or remove a role in your customizations, use the assignRolesWorkflow and unassignRolesWorkflow workflows. For example:

Code
import {
  assignRolesWorkflow,
} from "@medusajs/medusa/core-flows"

await assignRolesWorkflow(container).run({
  input: {
    assignments: [
      {
        role_id: "role_123",
        reference: "user",
        reference_id: "user_123",
      },
    ],
    granting_actor_id: "user_456",
  },
})

When you pass a granting_actor_id, the workflow checks that the granting actor already has all the policies of the roles being assigned. This prevents an admin user from granting access that they don't have themselves.

The granting actor is an admin user by default. If the granting actor is of another actor type, pass its type in the granting_actor input, such as vendor_admin.

When you remove a role with the unassignRolesWorkflow without passing a scope in an assignment, the workflow removes the entity's assignments of that role in all scopes, as well as its unscoped assignment. Pass a scope to only remove the assignment in that scope.

Learn how to retrieve an entity's roles in the Links to Other Modules guide.

Scoped Assignments#

An assignment's scope and scope_id restrict the role to a single entity instance. For example, an assignment whose scope is vendor and whose scope_id is ven_123 grants its policies on requests acting within that vendor.

A request only acts within a scope if your application sets one, as explained in the Permission Resolution section. Requests without a scope only consider unscoped assignments.

When you assign a role with the assignRolesWorkflow, you pass the two values as one scope object instead:

Code
import {
  assignRolesWorkflow,
} from "@medusajs/medusa/core-flows"

await assignRolesWorkflow(container).run({
  input: {
    assignments: [
      {
        role_id: "role_123",
        reference: "user",
        reference_id: "user_123",
        scope: { type: "vendor", id: "ven_123" },
      },
    ],
    granting_actor_id: "user_456",
    granting_scope: { type: "vendor", id: "ven_123" },
  },
})

The workflow stores the object's type in the assignment's scope property, and its id in the scope_id property.

Granting Scope#

When you assign a role, you can pass a granting_scope to restrict the granting actor's effective policies to a specific scope. This prevents a user from granting a role in a scope where they don't have access.

In the above example, the user assigning the role can only grant it for the ven_123 vendor, since their effective policies are resolved in that scope. They can't assign the role for another vendor, since they don't have access there.

If you omit granting_scope, the workflow only considers the granting actor's unscoped assignments. A user whose roles are all scoped to a vendor can't assign roles, even within that vendor, so pass granting_scope whenever the grant happens within a scope.

An unscoped assignment always counts, in both cases. For example, a Super Admin can assign roles in any scope, since they have the *:* wildcard policy without a scope.

Medusa's own resources don't use scopes. An assignment without a scope applies to every request.


Permission Resolution#

When the RBAC Module is installed, Medusa checks an admin user's effective policies before allowing an operation on a resource. A request succeeds if the user has a matching policy, including a wildcard policy that covers the resource or operation. Otherwise, the request fails with a 403 error.

Medusa resolves the roles of the request's actor for the scope the request acts within, considering the assignments that are unscoped or scoped to it. Your application sets that scope in a middleware that runs before the route's access check. Medusa's own routes don't set a scope, and requests without one only consider the actor's unscoped assignments.

Medusa also applies the policies to the fields a request asks for. If the actor can't read a related resource, Medusa removes the fields of that resource from the response instead of rejecting the request. This applies to routes that check permissions and whose query configuration specifies an entity. Learn more in the Protect API Routes guide.

When the RBAC Module isn't installed, Medusa enforces no access control, and admin users have access to all resources and operations.

Permission Caching#

When you use the Caching Module, Medusa caches the policies of each role for 24 hours to avoid retrieving them from the database on every request.

Medusa clears the cached policies when the RBAC Module creates, updates, deletes, or restores roles, policies, or the links between them. So, changes you make with Medusa's API routes, workflows, or the RBAC Module's service apply immediately.

Warning: If you change RBAC data without the RBAC Module's service, such as with raw SQL queries, Medusa doesn't clear the cache. Users keep their old permissions until the cached entries expire.

API Key Actor Type#

Requests authenticated with a secret API key have the api-key actor type. By default, a secret API key acts with the roles of the admin user who created it. Learn how to change this behavior in the Module Options guide.

Was this page helpful?
Ask Bloom
For assistance in your development, use Claude Code Plugins or Docs MCP server in Cursor, VSCode, etc...FAQ
What is Medusa?
How can I create a module?
How can I create a data model?
How do I create a workflow?
How can I extend a data model in the Product Module?
Recipes
How do I build a marketplace with Medusa?
How do I build digital products with Medusa?
How do I build subscription-based purchases with Medusa?
What other recipes are available in the Medusa documentation?
Chat is cleared on refresh
Line break
⇧↵