Claude
Skills
Sign in
Back

building-with-medusa

Included with Lifetime
$97 forever

Load automatically when planning, researching, or implementing ANY Medusa backend features (custom modules, API routes, workflows, data models, module links, business logic). REQUIRED for all Medusa backend work in ALL modes (planning, implementation, exploration). Contains architectural patterns, best practices, and critical rules that MCP servers don't provide.

Backend & APIs

What this skill does


# Medusa Backend Development

Comprehensive backend development guide for Medusa applications. Contains patterns across 6 categories covering architecture, type safety, business logic placement, and common pitfalls.

## When to Apply

**Load this skill for ANY backend development task, including:**
- Creating or modifying custom modules and data models
- Implementing workflows for mutations
- Building API routes (store or admin)
- Defining module links between entities
- Writing business logic or validation
- Querying data across modules
- Implementing authentication/authorization

**Also load these skills when:**
- **building-admin-dashboard-customizations:** Building admin UI (widgets, pages, forms)
- **building-storefronts:** Calling backend API routes from storefronts (SDK integration)

## CRITICAL: Load Reference Files When Needed

**The quick reference below is NOT sufficient for implementation.** You MUST load relevant reference files before writing code for that component.

**Load these references based on what you're implementing:**

- **Creating a module?** → MUST load `reference/custom-modules.md` first
- **Creating workflows?** → MUST load `reference/workflows.md` first
- **Creating API routes?** → MUST load `reference/api-routes.md` first
- **Creating module links?** → MUST load `reference/module-links.md` first
- **Querying data?** → MUST load `reference/querying-data.md` first
- **Adding authentication?** → MUST load `reference/authentication.md` first

**Minimum requirement:** Load at least 1-2 reference files relevant to your specific task before implementing.

## Critical Architecture Pattern

**ALWAYS follow this flow - never bypass layers:**

```
Module (data models + CRUD operations)
  ↓ used by
Workflow (business logic + mutations with rollback)
  ↓ executed by
API Route (HTTP interface, validation middleware)
  ↓ called by
Frontend (admin dashboard/storefront via SDK)
```

**Key conventions:**
- Only GET, POST, DELETE methods (never PUT/PATCH)
- Workflows are required for ALL mutations
- Business logic belongs in workflow steps, NOT routes
- Query with `query.graph()` for cross-module data retrieval
- Query with `query.index()` (Index Module) for filtering across separate modules with links
- Module links maintain isolation between modules

## Rule Categories by Priority

| Priority | Category | Impact | Prefix |
|----------|----------|--------|--------|
| 1 | Architecture Violations | CRITICAL | `arch-` |
| 2 | Type Safety | CRITICAL | `type-` |
| 3 | Business Logic Placement | HIGH | `logic-` |
| 4 | Import & Code Organization | HIGH | `import-` |
| 5 | Data Access Patterns | MEDIUM (includes CRITICAL price rule) | `data-` |
| 6 | File Organization | MEDIUM | `file-` |

## Quick Reference

### 1. Architecture Violations (CRITICAL)

- `arch-workflow-required` - Use workflows for ALL mutations, never call module services from routes
- `arch-layer-bypass` - Never bypass layers (route → service without workflow)
- `arch-http-methods` - Use only GET, POST, DELETE (never PUT/PATCH)
- `arch-module-isolation` - Use module links, not direct cross-module service calls
- `arch-query-config-fields` - Don't set explicit `fields` when using `req.queryConfig`

### 2. Type Safety (CRITICAL)

- `type-request-schema` - Pass Zod inferred type to `MedusaRequest<T>` when using `req.validatedBody`
- `type-authenticated-request` - Use `AuthenticatedMedusaRequest` for protected routes (not `MedusaRequest`)
- `type-export-schema` - Export both Zod schema AND inferred type from middlewares
- `type-linkable-auto` - Never add `.linkable()` to data models (automatically added)
- `type-module-name-camelcase` - Module names MUST be camelCase, never use dashes (causes runtime errors)

### 3. Business Logic Placement (HIGH)

- `logic-workflow-validation` - Put business validation in workflow steps, not API routes
- `logic-ownership-checks` - Validate ownership/permissions in workflows, not routes
- `logic-module-service` - Keep modules simple (CRUD only), put logic in workflows

### 4. Import & Code Organization (HIGH)

- `import-top-level` - Import workflows/modules at file top, never use `await import()` in route body
- `import-static-only` - Use static imports for all dependencies
- `import-no-dynamic-routes` - Dynamic imports add overhead and break type checking

### 5. Data Access Patterns (MEDIUM)

- `data-price-format` - **CRITICAL**: Prices are stored as-is in Medusa (49.99 stored as 49.99, NOT in cents). Never multiply by 100 when saving or divide by 100 when displaying
- `data-query-method` - Use `query.graph()` for retrieving data; use `query.index()` (Index Module) for filtering across linked modules
- `data-query-graph` - Use `query.graph()` for cross-module queries with dot notation (without cross-module filtering)
- `data-query-index` - Use `query.index()` when filtering by properties of linked data models in separate modules
- `data-list-and-count` - Use `listAndCount` for single-module paginated queries
- `data-linked-filtering` - `query.graph()` can't filter by linked module fields - use `query.index()` or query from that entity directly
- `data-no-js-filter` - Don't use JavaScript `.filter()` on linked data - use database filters (`query.index()` or query the entity)
- `data-same-module-ok` - Can filter by same-module relations with `query.graph()` (e.g., product.variants)
- `data-auth-middleware` - Trust `authenticate` middleware, don't manually check `req.auth_context`

### 6. File Organization (MEDIUM)

- `file-workflow-steps` - Recommended: Create steps in `src/workflows/steps/[name].ts`
- `file-workflow-composition` - Composition functions in `src/workflows/[name].ts`
- `file-middleware-exports` - Export schemas and types from middleware files
- `file-links-directory` - Define module links in `src/links/[name].ts`

## Workflow Composition Rules

**The workflow function has critical constraints:**

```typescript
// ✅ CORRECT
const myWorkflow = createWorkflow(
  "name",
  function (input) { // Regular function, not async, not arrow
    const result = myStep(input) // No await
    return new WorkflowResponse(result)
  }
)

// ❌ WRONG
const myWorkflow = createWorkflow(
  "name",
  async (input) => { // ❌ No async, no arrow functions
    const result = await myStep(input) // ❌ No await
    if (input.condition) { /* ... */ } // ❌ No conditionals
    return new WorkflowResponse(result)
  }
)
```

**Constraints:**
- No async/await (runs at load time)
- No arrow functions (use `function`)
- No conditionals/ternaries (use `when()`)
- No variable manipulation (use `transform()`)
- No date creation (use `transform()`)
- Multiple step calls need `.config({ name: "unique-name" })` to avoid conflicts

## Common Mistakes Checklist

Before implementing, verify you're NOT doing these:

**Architecture:**
- [ ] Calling module services directly from API routes
- [ ] Using PUT or PATCH methods
- [ ] Bypassing workflows for mutations
- [ ] Setting `fields` explicitly with `req.queryConfig`
- [ ] Skipping migrations after creating module links

**Type Safety:**
- [ ] Forgetting `MedusaRequest<SchemaType>` type argument
- [ ] Using `MedusaRequest` instead of `AuthenticatedMedusaRequest` for protected routes
- [ ] Not exporting Zod inferred type from middlewares
- [ ] Adding `.linkable()` to data models
- [ ] Using dashes in module names (must be camelCase)

**Business Logic:**
- [ ] Validating business rules in API routes
- [ ] Checking ownership in routes instead of workflows
- [ ] Manually checking `req.auth_context?.actor_id` when middleware already applied

**Imports:**
- [ ] Using `await import()` in route handler bodies
- [ ] Dynamic imports for workflows or modules

**Data Access:**
- [ ] **CRITICAL**: Multiplying prices by 100 when saving or dividing by 100 when displaying (prices are stored as-is: $49.99 = 49.99)
- [ ] Filtering by linked module fields with `query.graph()` (use `query.index()` or query from other side instead)
- [ ] Using JavaScri

Related in Backend & APIs