Adding Custom Actors
May 1, 2026 · 5 min read
Actors in medusa-permissions represent entities that perform actions in your system. While the plugin comes with built-in actors for users and customers, you can create custom actors to support additional entity types like vendors, partners, or team members.
#Understanding Actor Providers
Actor providers are responsible for:
- Resolving actors to their associated roles
- Listing roles for a specific actor
- Managing actor-role assignments
- Listing actors of a specific type
#Actor Provider Structure
All custom actor providers extend AbstractActorResolver:
export abstract class AbstractActorResolver {
static identifier: string;
static display_name: string;
static actor_type: string;
constructor(
public readonly actor_type: string,
protected readonly container: MedusaContainer["cradle"],
protected readonly options: Record<string, any>
) {}
abstract getActorDetails(input: ActorResolverInput): Promise<ActorResolverDetailsOutput>;
abstract listRoles(input: ActorResolverInput): Promise<ActorResolverOutput>;
abstract listActors(input: ActorResolverListInput): Promise<ActorResolverListOutput[]>;
abstract updateActorRoles(input: ActorResolverUpdateRolesInput): Promise<ActorResolverOutput>;
}
#Creating a Custom Actor: Vendor Example
Here's a complete example of creating a vendor actor provider:
import { MedusaContainer } from "@medusajs/framework/types"
import {
AbstractActorResolver,
type ActorResolverInput,
type ActorResolverDetailsOutput,
type ActorResolverOutput,
type ActorResolverListInput,
type ActorResolverListOutput,
type ActorResolverUpdateRolesInput
} from "medusa-permissions"
class VendorActorResolver extends AbstractActorResolver {
static identifier = "vendor"
static display_name = "Vendor"
static actor_type = "vendor"
constructor(
public readonly actor_type: string,
protected readonly container: MedusaContainer["cradle"],
protected readonly options: Record<string, any>
) {
super(actor_type, container, options)
}
async getActorDetails(input: ActorResolverInput): Promise<ActorResolverDetailsOutput> {
const { actor_id } = input;
// Query your vendor entity
const vendorService = this.container.vendorService;
const vendor = await vendorService.retrieveVendor(actor_id);
if (!vendor) {
throw new Error(`Vendor with ID ${actor_id} not found`);
}
return {
actor_id: vendor.id,
actor_type: this.actor_type,
display_name: vendor.name,
metadata: {
email: vendor.email,
status: vendor.status
}
};
}
async listRoles(input: ActorResolverInput): Promise<ActorResolverOutput> {
const { actor_id } = input;
const permissionsService = this.container.permissionsService;
// Get roles assigned to this vendor
const roles = await permissionsService.listActorRoles({
actor_id,
actor_type: this.actor_type
});
return {
actor_id,
actor_type: this.actor_type,
roles: roles.map(r => ({
id: r.id,
name: r.name,
priority: r.priority
}))
};
}
async listActors(input: ActorResolverListInput): Promise<ActorResolverListOutput[]> {
const { limit = 50, offset = 0 } = input;
// Query your vendor service
const vendorService = this.container.vendorService;
const vendors = await vendorService.listVendors({
take: limit,
skip: offset
});
return vendors.map(vendor => ({
actor_id: vendor.id,
actor_type: this.actor_type,
display_name: vendor.name,
metadata: {
email: vendor.email
}
}));
}
async updateActorRoles(input: ActorResolverUpdateRolesInput): Promise<ActorResolverOutput> {
const { actor_id, role_ids } = input;
const permissionsService = this.container.permissionsService;
// Update vendor roles
await permissionsService.assignRolesToActor({
actor_id,
actor_type: this.actor_type,
role_ids
});
return this.listRoles({ actor_id });
}
}
export default VendorActorResolver;
#Type Definitions
#ActorResolverInput
interface ActorResolverInput {
actor_id: string;
}
#ActorResolverDetailsOutput
interface ActorResolverDetailsOutput {
actor_id: string;
actor_type: string;
display_name: string;
metadata?: Record<string, any>;
}
#ActorResolverOutput
interface ActorResolverOutput {
actor_id: string;
actor_type: string;
roles: Array<{
id: string;
name: string;
priority?: number;
}>;
}
#ActorResolverListInput
interface ActorResolverListInput {
limit?: number;
offset?: number;
}
#ActorResolverListOutput
interface ActorResolverListOutput {
actor_id: string;
actor_type: string;
display_name: string;
metadata?: Record<string, any>;
}
#ActorResolverUpdateRolesInput
interface ActorResolverUpdateRolesInput {
actor_id: string;
role_ids: string[];
}
#Registering Your Actor
Add your custom actor to medusa-config.ts:
{
resolve: "medusa-permissions/modules/permissions",
options: {
permissions: [
{
resolve: "medusa-permissions/permissions/admin"
}
],
actors: [
{
resolve: "medusa-permissions/actors/user"
},
{
resolve: "medusa-permissions/actors/customer"
},
{
resolve: "./src/services/permissions/vendor-actor" // Your custom actor
}
]
}
}
#Integration with Middlewares
Once your actor is registered, it can be used in permission middlewares:
import { validatePermission } from "medusa-permissions/utils/permission-middleware";
export default defineMiddlewares({
routes: [
{
method: "GET",
matcher: "/admin/vendor/:id",
middlewares: [
validatePermission("vendor.orders.list")
]
}
]
});
Document outline