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 accessed
  • store_id - The store ID
  • sales_channel_id - Sales channel identifier
  • region_id - Region identifier
  • stock_location_id - Stock location identifier
  • customer_group_id - Customer group identifier

Actor Scoping

  • actor_id - The ID of the actor performing the action
  • actor_type - Type of actor (user, customer, vendor, etc.)

Field and Route

  • fields - Specific fields being accessed/modified
  • route - The API route being accessed

Role Hierarchy

  • permission - Current permission being evaluated
  • permission_hierarchy - Full permission hierarchy
  • target_role - The role being operated on
  • target_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:

  1. Rule Priority - Explicit priority values (higher = more important)
  2. Specificity - More specific conditions take precedence
  3. 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_id matches 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_id equals request context
  • AND region_id equals request context
  • AND target_role_is_lower_priority is 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: fields includes (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:

ExtensionsPermissionsAudit 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