A Mercur module is a standard Medusa module. The examples below build a small Brand module. It is the kind of custom module you add to your own project alongside the built-in ones, so the rules stand on their own rather than relying on Mercur internals.
Thin CRUD only
A module service exists to read and write its own tables. ExtendMedusaService({ ...models }) and you get typed list, listAndCount, retrieve, create, update, and delete methods for every model for free. Use them.
Define the model with plain columns.
src/modules/brand/models/brand.ts
MedusaService to get the generated CRUD methods.
src/modules/brand/service.ts
src/modules/brand/service.ts
Naming
Follow four conventions so the module reads like the built-in ones.-
Register the module by a stable id constant. Export the module id and register the service against it:
src/modules/brand/index.ts
-
Methods are
camelCaseand model-suffixed. Medusa generateslistBrands,createBrands, andretrieveBrand. Match that casing and pluralisation when you add or override methods. Private helpers end with a trailing underscore (computeBrandStats_). -
Models are lowercase-defined, referenced by their key.
model.define("brand", { ... }). The object key you pass toMedusaService(Brand) is what drives the generated method names. -
Types live next to the module, or in a shared types package. Export DTOs such as
BrandDTOand import them. Never redeclare a model’s shape ad hoc. See Types & augmentation.
Do not call .linkable(): links are declared separately
It is tempting to relate two modules by pointing a model at another module’s table. Don’t. A module model must not reference another module’s data, and you should not wire relationships inside the model definition.
Define the relationship as its own link file instead. This is covered in full on Module links.
Relationship declared as a link, not inside the model
src/modules/brand/models/brand.ts
Decorators
Custom service methods that touch the database use Medusa’s dependency-injection decorators so they participate in the ambient transaction and shared context.Decorator pattern for a custom write
Checklist for a module
- Extends
MedusaService({ ...models })and leans on generated CRUD. - Registered with
Module(BRAND_MODULE, { service })against a stable id. - No import of, or call into, any other module’s service.
- Models are flat: no
.linkable()wiring, no cross-module foreign keys. - Custom methods use
@InjectManager/@InjectTransactionManagerplus@MedusaContext. - DTOs are exported and imported, never redeclared inline.
- No orchestration, no events, no HTTP. Those live in workflows and routes.
Next steps
Workflows
Orchestrate business operations across modules, with compensation on failure.
Module links
Relate two modules with
defineLink and read the relationship through Query.Types & augmentation
Export DTOs and share a model’s shape instead of redeclaring it inline.
Best practices overview
See the layered architecture and the logic-placement cheat sheet.