MedusaJS Plugins / RBAC Permissions/user-defined-permissions

User-Defined Permissions

May 1, 2026 · 5 min read

Custom permissions allow you to extend the medusa-permissions system to protect your own module APIs and custom endpoints. This guide shows you how to define, configure, and enforce custom permissions.

Understanding Permission Definitions

Permission definitions specify what permissions are available in your system. They include:

  • Permission key: Unique hierarchical identifier (e.g., vendor.orders.manage)
  • Description: User-friendly description
  • Parameters: Dynamic conditions that affect permission evaluation

Creating Permission Definitions

Use the definePermission helper to create custom permissions:

import { definePermission } from "medusa-permissions/modules/permissions/definitions"

export default [
  definePermission({
    key: "vendor.orders.manage",
    description: "Manage Vendor Orders",
    params: [],
  }),
  definePermission({
    key: "vendor.products.create",
    description: "Create Products",
    params: [],
  }),
  definePermission({
    key: "vendor.analytics.view",
    description: "View Analytics",
    params: [],
  }),
]

Adding Parameters to Permissions

Parameters enable context-aware permissions. You can use built-in or custom parameters:

import { definePermission, resolveContextValue } from "medusa-permissions"
import { z } from "zod"

const isOwnerParam = {
  name: "is_owner",
  resolver: resolveContextValue("is_owner"),
  description: "Check if actor owns the resource"
}

export default [
  definePermission({
    key: "vendor.orders.manage",
    description: "Manage Orders",
    params: [isOwnerParam],
  }),
  definePermission({
    key: "vendor.products.update",
    description: "Update Products",
    params: [
      {
        name: "product_owner",
        resolver: resolveContextValue("product_owner"),
        description: "Check if vendor owns the product"
      }
    ],
  }),
]

Permission Definition Structure

interface PermissionDefinition {
  // Unique hierarchical permission key
  key: string;

  // User-friendly description
  description: string;

  // Optional parameters for context-aware rules
  params?: PermissionParamDefinition[];

  // Optional metadata
  metadata?: Record<string, any>;
}

interface PermissionParamDefinition {
  // Parameter name
  name: string;

  // How to resolve the parameter value
  resolver: (context: any) => Promise<any> | any;

  // Optional description
  description?: string;
}

Registering Custom Permissions

Create a file for your custom permissions:

// src/permissions/vendor.ts
import { definePermission } from "medusa-permissions/modules/permissions/definitions"

export default [
  definePermission({
    key: "vendor.orders.list",
    description: "List Orders",
    params: [],
  }),
  definePermission({
    key: "vendor.orders.update",
    description: "Update Orders",
    params: [],
  }),
  definePermission({
    key: "vendor.products.manage",
    description: "Manage Products",
    params: [],
  }),
]

Then add to medusa-config.ts:

{
  resolve: "medusa-permissions/modules/permissions",
  options: {
    permissions: [
      {
        resolve: "medusa-permissions/permissions/admin"
      },
      {
        resolve: "./src/permissions/vendor" // Your custom permissions
      }
    ],
    // ... other options
  }
}

Enforcing Permissions in Middlewares

Once defined, enforce permissions in your API middlewares:

// src/api/middlewares.ts
import { defineMiddlewares } from "@medusajs/framework/http";
import {
  validatePermission,
  createPermissionContextMiddleware
} from "medusa-permissions/utils/permission-middleware";

const withVendorContext = createPermissionContextMiddleware((req) => ({
  vendor_id: req.vendor?.id,
  is_owner: req.vendor?.id === req.params?.vendor_id,
}))

export default defineMiddlewares({
  routes: [
    {
      method: "GET",
      matcher: "/admin/vendor/orders",
      middlewares: [
        withVendorContext,
        validatePermission("vendor.orders.list")
      ]
    },
    {
      method: "POST",
      matcher: "/admin/vendor/orders/:id",
      middlewares: [
        withVendorContext,
        validatePermission("vendor.orders.update")
      ]
    },
    {
      method: "POST",
      matcher: "/admin/vendor/products",
      middlewares: [
        withVendorContext,
        validatePermission("vendor.products.manage")
      ]
    },
  ]
});

Advanced: Using Custom Parameter Resolvers

Define custom parameter resolvers for complex permission logic:

import { definePermission } from "medusa-permissions"

const checkRegionAccess = async (context: any) => {
  const { vendor_id, region_id } = context;
  
  // Your custom logic to check if vendor has access to region
  const vendor = await vendorService.retrieveVendor(vendor_id, {
    relations: ["regions"]
  });
  
  return vendor.regions.some(r => r.id === region_id);
}

export default [
  definePermission({
    key: "vendor.regions.orders.list",
    description: "List Orders in Region",
    params: [
      {
        name: "region_access",
        resolver: checkRegionAccess,
        description: "Check if vendor has access to region"
      }
    ],
  }),
]

Permission Hierarchy

Permission keys support wildcard matching for flexible rule evaluation:

// Specific permission
admin.orders.update

// Matches wildcard patterns:
admin.orders.*      // All order actions
admin.*             // All admin actions
*                   // Everything

Rules are evaluated in order of specificity, with more specific rules taking precedence.

Field-Level Permissions

For advanced use cases, restrict specific fields in queries or mutations:

import { 
  withGlobalQueryPermission,
  withGlobalMutatePermission 
} from "medusa-permissions/utils/permission-middleware"

export default defineMiddlewares({
  routes: [
    {
      method: "GET",
      matcher: "/admin/orders",
      middlewares: [
        withGlobalQueryPermission("admin.api.query", ["id", "email", "total"])
      ]
    }
  ]
});

Example: Complete Permission Setup

// src/permissions/store.ts
import { definePermission } from "medusa-permissions"

export default [
  // Store management
  definePermission({
    key: "store.settings.update",
    description: "Update Store Settings",
    params: [],
  }),
  
  // Product management
  definePermission({
    key: "store.products.create",
    description: "Create Products",
    params: [],
  }),
  definePermission({
    key: "store.products.update",
    description: "Update Products",
    params: [],
  }),
  definePermission({
    key: "store.products.delete",
    description: "Delete Products",
    params: [],
  }),

  // Order management
  definePermission({
    key: "store.orders.view",
    description: "View Orders",
    params: [],
  }),
  definePermission({
    key: "store.orders.update",
    description: "Update Orders",
    params: [],
  }),

  // Analytics
  definePermission({
    key: "store.analytics.view",
    description: "View Analytics",
    params: [],
  }),
]

Extending Built-in Permissions

You can extend existing permission keys by re-registering them with additional parameters:

export default [
  definePermission({
    key: "admin.orders.update",
    description: "Update Orders",
    params: [
      {
        name: "sales_channel_id",
        resolver: resolveContextValue("sales_channel_id"),
      }
    ],
  }),
]

This merges your parameters with existing ones rather than replacing the definition.