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.