dist/cursor/medusa-commerce/skills/medusa-modules/SKILL.md
Build custom Medusa v2 modules — DML data models, services extending MedusaService, loaders, module links, and container registration. Use when creating custom modules.
npx skillsauth add orcaqubits/agentic-commerce-claude-plugins medusa-modulesInstall 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/modules for module overviewsite:docs.medusajs.com DML data model reference for model property typessite:docs.medusajs.com MedusaService methods for base service APIsite:docs.medusajs.com module links for linking modules togethersite:docs.medusajs.com module container registration for dependency injectionA module is a self-contained package of functionality in Medusa v2:
medusa-config.ts under the modules array| File | Purpose |
|------|---------|
| src/modules/my-module/models/my-entity.ts | DML data model |
| src/modules/my-module/service.ts | Service extending MedusaService |
| src/modules/my-module/loaders/seed.ts | Optional loader (runs on startup) |
| src/modules/my-module/migrations/ | Auto-generated by CLI |
| src/modules/my-module/index.ts | Module definition export |
| DML Type | Database Column | Notes |
|----------|----------------|-------|
| model.id() | Primary key UUID | Auto-generated |
| model.text() | text / varchar | String fields |
| model.number() | integer | Numeric fields |
| model.bigNumber() | numeric | Precise decimals (prices) |
| model.boolean() | boolean | True/false |
| model.dateTime() | timestamptz | Date/time values |
| model.json() | jsonb | Arbitrary JSON |
| model.enum() | enum | Constrained values |
| model.array() | array | Array of primitives |
| Relationship | DML Method | Description |
|-------------|-----------|-------------|
| Has One | model.hasOne() | One-to-one owned |
| Has Many | model.hasMany() | One-to-many owned |
| Belongs To | model.belongsTo() | Inverse of hasOne/hasMany |
| Many to Many | model.manyToMany() | Junction table auto-created |
// src/modules/my-module/models/my-entity.ts
// Fetch live docs for current DML model API
import { model } from "@medusajs/framework/utils"
const MyEntity = model.define("my_entity", {
id: model.id(), name: model.text(),
// Fetch live docs for property options and relationships
})
Every module exposes a service that extends MedusaService:
// src/modules/my-module/service.ts
// Fetch live docs for MedusaService generic signature
import { MedusaService } from "@medusajs/framework/utils"
import MyEntity from "./models/my-entity"
class MyModuleService extends MedusaService({ MyEntity }) {}
export default MyModuleService // Add custom methods as needed
| Method Pattern | Purpose |
|---------------|---------|
| create<Entity>(data) | Create one or many records |
| update<Entity>(data) | Update records by selector or ID |
| delete<Entity>(ids) | Soft-delete records |
| retrieve<Entity>(id, config) | Retrieve single record by ID |
| list<Entity>(filters, config) | List with filters, pagination, relations |
| softRestore<Entity>(ids) | Restore soft-deleted records |
The method names are auto-generated from the model name (e.g., createMyEntity, listMyEntities).
// src/modules/my-module/index.ts
// Fetch live docs for Module definition shape
import { Module } from "@medusajs/framework/utils"
import MyModuleService from "./service"
export const MY_MODULE = Module("my-module", {
service: MyModuleService,
})
// Inside modules array
// Fetch live docs for module registration options
{ resolve: "./src/modules/my-module" }
Links connect data models across different modules without coupling:
| Link Type | Purpose | |-----------|---------| | One-to-one link | Connect a custom entity to a core entity (e.g., product) | | One-to-many link | One core entity linked to many custom entities | | List link | Bidirectional many-to-many between modules |
// src/links/product-my-entity.ts
// Fetch live docs for defineLink API
import { defineLink } from "@medusajs/framework/utils"
import ProductModule from "@medusajs/medusa/product"
import { MY_MODULE } from "../modules/my-module"
Links are queried using the query utility from the Medusa container, not through direct service calls.
Loaders run when the module initializes (server startup):
Modules register their service in the dependency injection container:
container.resolve("my-module")MedusaService for CRUD -- only add custom methods for non-standard logicnpx medusa db:generate after every model change to create migrationsMY_MODULE) and reuse it everywhere| Pattern | When to Use | |---------|------------| | Custom module + link to Product | Adding metadata/features to products | | Custom module + link to Order | Order-related extensions (loyalty, tracking) | | Standalone module | Independent domain (CMS, analytics, reviews) | | Module with loader | Pre-seeding data or external service setup |
Fetch the Medusa module documentation for exact DML property options, MedusaService generics, and link definition patterns 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.