Parameter Scoping Usage
May 1, 2026 · 5 min read
Parameter scoping is the most powerful feature of medusa-permissions, enabling Attribute-Based Access Control (ABAC) within your RBAC system. This guide demonstrates how to use parameters to create context-aware permission rules.
#What is Parameter Scoping?
Parameter scoping allows permission rules to evaluate runtime conditions from the request context. Instead of just checking roles, you can verify:
- Who is making the request?
- What resource are they accessing?
- In which context (sales channel, region, etc.)?
- What are their relationships to the resource?
#Built-in Parameters
The system provides several built-in parameters ready to use:
#Resource and Tenancy
resource_id- The ID of the resource being accessedstore_id- The store IDsales_channel_id- Sales channel identifierregion_id- Region identifierstock_location_id- Stock location identifiercustomer_group_id- Customer group identifier
#Actor Scoping
actor_id- The ID of the actor performing the actionactor_type- Type of actor (user, customer, vendor, etc.)
#Field and Route
fields- Specific fields being accessed/modifiedroute- The API route being accessed
#Role Hierarchy
permission- Current permission being evaluatedpermission_hierarchy- Full permission hierarchytarget_role- The role being operated ontarget_role_is_lower_priority- Whether the target role has lower priority
#Understanding Rule Priority and Specificity
The permission system evaluates rules using a priority system:
- Rule Priority - Explicit priority values (higher = more important)
- Specificity - More specific conditions take precedence
- Deny-First - Deny rules override allow rules
#Creating Permission Rules with Parameters
When defining permissions in the admin interface or through role assignments, you can scope rules with parameters.
#Example 1: Sales Channel Scoping
Allow managers to view orders only in their assigned sales channels:
// Permission definition
definePermission({
key: "admin.orders.list",
description: "List Orders",
params: [
{
name: "sales_channel_id",
resolver: resolveContextValue("sales_channel_id"),
description: "Sales channel ID from request context"
}
]
})
Then in your middleware:
import { createPermissionContextMiddleware } from "medusa-permissions"
const withSalesChannelContext = createPermissionContextMiddleware((req) => {
return {
sales_channel_id: req.query.sales_channel_id || req.user.assigned_channel_id,
}
})
export default defineMiddlewares({
routes: [
{
method: "GET",
matcher: "/admin/orders",
middlewares: [
withSalesChannelContext,
validatePermission("admin.orders.list")
]
}
]
})
In the admin UI, create a rule:
- Permission:
admin.orders.list - Effect: Allow
- Conditions:
sales_channel_idmatches request context
#Example 2: Role Hierarchy Scoping
Ensure managers can only manage roles lower in the hierarchy:
// Permission definition
definePermission({
key: "admin.permissions.roles.update",
description: "Update Roles",
params: [
{
name: "target_role_is_lower_priority",
resolver: resolveContextValue("target_role_is_lower_priority"),
description: "Whether target role is lower priority than actor's highest role"
}
]
})
Middleware with hierarchy detection:
import { createPermissionContextMiddleware } from "medusa-permissions"
const withRoleHierarchyContext = createPermissionContextMiddleware(async (req) => {
const roleId = req.params.id;
const currentUser = req.user;
// Get the role being updated
const targetRole = await roleService.retrieveRole(roleId);
// Get current user's highest role priority
const userRoles = await permissionsService.listActorRoles({
actor_id: currentUser.id,
actor_type: "user"
});
const userHighestPriority = Math.max(...userRoles.map(r => r.priority));
return {
target_role: roleId,
target_role_is_lower_priority: targetRole.priority < userHighestPriority,
}
})
#Example 3: Resource Ownership
Only allow users to manage resources they own:
// Permission definition
definePermission({
key: "vendor.products.update",
description: "Update Products",
params: [
{
name: "is_owner",
resolver: resolveContextValue("is_owner"),
description: "Check if vendor owns the product"
}
]
})
Middleware with ownership check:
const withOwnershipContext = createPermissionContextMiddleware(async (req) => {
const productId = req.params.id;
const vendorId = req.vendor.id;
const product = await productService.retrieveProduct(productId);
return {
resource_id: productId,
is_owner: product.vendor_id === vendorId,
}
})
#Creating Custom Parameter Resolvers
For complex logic, create custom parameter resolvers:
// src/permissions/parameters.ts
export const checkRegionAccess = async (context: any) => {
const { actor_id, region_id } = context;
const permissionsService = context.container.permissionsService;
// Check if user has region assignment
const hasAccess = await permissionsService.checkActorRegionAccess(
actor_id,
region_id
);
return hasAccess;
}
export const checkOrderOwnership = async (context: any) => {
const { actor_id, resource_id } = context;
const orderService = context.container.orderService;
const order = await orderService.retrieveOrder(resource_id);
return order.customer_id === actor_id;
}
// Use in permissions
export default [
definePermission({
key: "orders.view",
description: "View Orders",
params: [
{
name: "region_access",
resolver: checkRegionAccess,
description: "Check region access"
}
]
}),
definePermission({
key: "orders.update",
description: "Update Orders",
params: [
{
name: "ownership",
resolver: checkOrderOwnership,
description: "Check order ownership"
}
]
}),
]
#Multi-Parameter Rules
Combine multiple parameters for sophisticated rules:
// Create a rule requiring multiple conditions:
// - User must have the permission
// - User must have access to the sales channel
// - User must have access to the region
// - The target role must be lower priority
const context = {
sales_channel_id: req.query.channel_id,
region_id: req.query.region_id,
target_role_is_lower_priority: isLowerPriority,
actor_id: req.user.id,
}
In the admin UI, rules can require:
sales_channel_idequals request context- AND
region_idequals request context - AND
target_role_is_lower_priorityis true - Effect: Allow
#Field-Level Scoping
Restrict access to specific fields:
const withFieldContext = createPermissionContextMiddleware((req) => ({
fields: Object.keys(req.body), // Fields being updated
route: req.url,
}))
export default defineMiddlewares({
routes: [
{
method: "PATCH",
matcher: "/admin/products/:id",
middlewares: [
withFieldContext,
validatePermission("admin.products.update")
]
}
]
})
Then create rules:
- Permission:
admin.products.update - Conditions:
fieldsincludes (price OR discount) - Effect: Deny (restrict price/discount changes for certain roles)
#Dynamic Parameter Computation
Parameters can be computed from multiple request sources:
const withDynamicContext = createPermissionContextMiddleware(async (req) => {
const { id } = req.params;
const { action } = req.query;
// Combine multiple sources
const resource = await getResource(id);
const actor = req.user;
return {
resource_id: id,
resource_owner_id: resource.owner_id,
actor_id: actor.id,
is_owner: resource.owner_id === actor.id,
action: action,
// Computed value
can_edit: resource.owner_id === actor.id && resource.status !== "archived",
}
})
#Audit Logs with Parameters
All parameter evaluations are logged. Check audit logs to verify parameter resolution:
Extensions → Permissions → Audit Logs
Click on a log entry to see:
- Resolved parameters
- Matched rule
- Matched role
- Decision outcome
#Layered Middleware Pattern
Combine multiple middleware to build complex contexts:
const withBaseContext = createPermissionContextMiddleware((req) => ({
actor_id: req.user.id,
route: req.url,
}))
const withResourceContext = createPermissionContextMiddleware(async (req) => ({
resource_id: req.params.id,
// Async operations are fine
}))
const withOwnershipContext = createPermissionContextMiddleware(async (req) => ({
is_owner: await checkOwnership(req),
}))
export default defineMiddlewares({
routes: [
{
method: "PATCH",
matcher: "/admin/resource/:id",
middlewares: [
withBaseContext,
withResourceContext,
withOwnershipContext,
validatePermission("admin.resource.update")
]
}
]
})
#Complete Example: Sales Team Access
// Define permission with parameters
definePermission({
key: "admin.orders.manage",
description: "Manage Orders",
params: [
{
name: "sales_channel_id",
resolver: resolveContextValue("sales_channel_id"),
},
{
name: "is_assigned",
resolver: resolveContextValue("is_assigned"),
}
]
})
// Middleware setup
const withSalesContext = createPermissionContextMiddleware(async (req) => {
const channelId = req.query.channel_id;
const user = req.user;
// Check if user is assigned to channel
const isAssigned = user.assigned_channels.includes(channelId);
return {
sales_channel_id: channelId,
is_assigned: isAssigned,
}
})
export default defineMiddlewares({
routes: [
{
method: "POST",
matcher: "/admin/orders/:id/confirm",
middlewares: [
withSalesContext,
validatePermission("admin.orders.manage")
]
}
]
})
// Admin UI rule:
// Effect: Allow
// Permission: admin.orders.manage
// Conditions:
// - sales_channel_id equals request context
// - is_assigned is true