dist/codex/medusa-commerce/skills/medusa-subscribers/SKILL.md
Implement Medusa v2 event subscribers — pub/sub event handling, subscriber handler functions, scheduled jobs with cron, and the event module (Redis or in-memory). Use when reacting to commerce events.
npx skillsauth add orcaqubits/agentic-commerce-claude-plugins medusa-subscribersInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Fetch live docs:
https://docs.medusajs.com/learn/fundamentals/events-and-subscribers for subscriber overviewsite:docs.medusajs.com subscriber handler function for handler APIsite:docs.medusajs.com scheduled jobs cron for scheduled job patternssite:docs.medusajs.com event module redis for event bus configurationsite:docs.medusajs.com built-in events list for available event namesSubscribers react to events emitted by Medusa workflows and services:
src/subscribers/config object{entity}.{action} (e.g., product.created)src/subscribers/
├── product-created.ts # Reacts to product.created
├── order-placed.ts # Reacts to order.placed
└── customer-registered.ts # Reacts to customer.created
Each file is auto-discovered -- no manual registration required.
// src/subscribers/product-created.ts
// Fetch live docs for SubscriberArgs and SubscriberConfig types
import type { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
export default async function productCreatedHandler(
{ event, container }: SubscriberArgs<{ id: string }>) {
// event.data.id contains the entity ID — fetch live docs for payload shapes
}
// Fetch live docs for SubscriberConfig options
export const config: SubscriberConfig = {
event: "product.created",
}
| Domain | Event Examples |
|--------|---------------|
| Products | product.created, product.updated, product.deleted |
| Orders | order.placed, order.canceled, order.completed |
| Customers | customer.created, customer.updated |
| Cart | cart.created, cart.updated |
| Fulfillment | fulfillment.created, fulfillment.canceled |
| Payment | payment.captured, payment.refunded |
| Inventory | inventory-item.created |
| Auth | invite.created, invite.accepted |
Fetch live docs for the complete event list -- events are added and renamed across Medusa releases.
A single subscriber can listen to multiple events:
// Fetch live docs for multi-event config
export const config: SubscriberConfig = {
event: ["product.created", "product.updated"],
}
The handler receives the event name in event.name to distinguish which event triggered it.
| Property | Type | Description |
|----------|------|-------------|
| event.name | string | The event that triggered the subscriber |
| event.data | object | Payload emitted by the workflow/service |
| event.metadata | object | Internal metadata (event ID, timestamp) |
The data shape varies per event. Typically contains the entity ID(s) affected.
| Module | Transport | Use Case | |--------|-----------|----------| | In-memory (default) | Process memory | Development, single-instance | | Redis Event Module | Redis Pub/Sub | Production, multi-instance |
// In medusa-config.ts modules array
// Fetch live docs for Redis event module configuration
{
resolve: "@medusajs/medusa/event-bus-redis",
options: { redisUrl: process.env.REDIS_URL },
}
In production, always use the Redis event module to ensure events are delivered across multiple server instances.
Scheduled jobs run on a cron schedule, independent of events:
src/jobs/
├── daily-sync.ts # Daily data synchronization
└── cleanup-expired.ts # Periodic cleanup
// src/jobs/daily-sync.ts
// Fetch live docs for MedusaContainer type
import type { MedusaContainer } from "@medusajs/framework"
export default async function dailySyncJob(container: MedusaContainer) {
const service = container.resolve("my-module")
// Fetch live docs for job handler API
}
// Fetch live docs for cron expression format
export const config = {
name: "daily-sync",
schedule: "0 0 * * *", // Midnight daily (cron syntax)
}
| Schedule | Cron Expression |
|----------|----------------|
| Every minute | * * * * * |
| Every 15 minutes | */15 * * * * |
| Every hour | 0 * * * * |
| Daily at midnight | 0 0 * * * |
| Weekly on Monday | 0 0 * * 1 |
| Monthly on the 1st | 0 0 1 * * |
| Worker Mode | Subscribers | Scheduled Jobs |
|-------------|------------|----------------|
| shared | Processed in-process | Processed in-process |
| server | Emitted only, not processed | Not processed |
| worker | Processed | Processed |
In production, run a dedicated worker instance to handle subscribers and scheduled jobs separately from HTTP traffic.
Custom events can be emitted from workflows using the emitEventStep:
// Inside a workflow
// Fetch live docs for emitEventStep import
import { emitEventStep } from "@medusajs/medusa/core-flows"
emitEventStep({ eventName: "custom.event", data: { id: "123" } })
SubscriberArgs<{ id: string }>Fetch the Medusa subscriber and events documentation for exact event names, payload shapes, and Redis event module configuration before implementing.
development
Build with Spree's headless Next.js storefront — the official `spree/storefront` repo (Next.js 16 App Router with Server Actions and Turbopack, React 19 Server Components, Tailwind CSS 4, TypeScript 5, `@spree/sdk`, Sentry), server-only auth (httpOnly JWT cookies + publishable key), MeiliSearch faceted catalog, one-page checkout with Apple/Google Pay/Klarna/Affirm/SEPA, multi-region market routing, GA4 + JSON-LD SEO, and Vercel/Docker deployment. Use when forking or customizing the storefront, or evaluating headless adoption.
tools
Build Spree extensions as Rails engines — gem scaffolding, `bin/rails g spree:extension`, mounting routes/migrations/assets, the modern `prepend` decorator pattern (`*_decorator.rb` with `self.prepended(base)`), generators (`spree:model_decorator`, `spree:controller_decorator`), the four customization surfaces in preference order (Events > Webhooks > Dependencies > Decorators), Spree::Dependencies for swapping service objects, gem release/versioning, and the deprecated Deface engine. Use when building a reusable Spree extension or adding non-trivial customization to an app.
development
Build with Spree's event bus and Webhooks 2.0 — `Spree::Events` publication, `Spree::Subscriber` DSL with `subscribes_to` and `on`, wildcard matching, lifecycle events (`{model}.created/.updated/.deleted` via `publishes_lifecycle_events`), the canonical event catalog (order.*, payment.*, shipment.*, product.*), Webhooks 2.0 endpoints, HMAC-SHA256 signing (`X-Spree-Webhook-Signature`), exponential-backoff retries, and Sidekiq job orchestration. Use when wiring event-driven business logic, building webhook consumers, or replacing ActiveSupport callback chains.
tools
Cross-cutting Spree development patterns — the customization preference hierarchy (Events > Webhooks > Dependencies > Decorators), `Spree::Dependencies` service-object swapping, the `_decorator.rb` + `prepend` + `self.prepended` idiom, idempotent subscribers and webhook receivers, multi-store scoping discipline, prefixed IDs, calculator polymorphism (shipping/promotion/tax share the base), service-object composition with `dry-monads` or simple results, why to avoid `class_eval` reopening and Deface, and Spree-on-Rails idioms (Hotwire/Turbo Stimulus, ActiveStorage, Action Cable, Sidekiq). Use when designing the architecture of a Spree extension or solving cross-cutting concerns.