dist/codex/medusa-commerce/skills/ts-modern/SKILL.md
Write modern TypeScript for Medusa v2 — DML type inference, service generics, container typing, strict mode constraints, utility types, and module interface patterns. Use when writing TypeScript in Medusa projects.
npx skillsauth add orcaqubits/agentic-commerce-claude-plugins ts-modernInstall 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:
site:docs.medusajs.com typescript types modules for Medusa type patternssite:www.typescriptlang.org docs handbook for latest TypeScript featuressite:docs.medusajs.com DML infer type model for DML type inferencehttps://docs.medusajs.com/learn/fundamentals/modules/service-factory for service genericssite:docs.medusajs.com container dependency injection resolve for container typingMedusa v2 projects use strict TypeScript with specific compiler options:
| Option | Value | Purpose |
|--------|-------|---------|
| strict | true | Enable all strict type checks |
| target | ES2021 | Modern JS output |
| module | CommonJS | Module system |
| moduleResolution | node | Node.js module resolution |
| esModuleInterop | true | CJS/ESM interop |
| skipLibCheck | true | Skip declaration file checks |
| declaration | true | Generate .d.ts files |
| outDir | .medusa/server | Build output directory |
Medusa generates TypeScript types from DML model definitions. Use InferTypeOf to derive entity types without manual interface duplication:
// Fetch live docs for InferTypeOf import path
// and exact DML-to-type mapping behavior
import { InferTypeOf } from "@medusajs/framework/types"
| DML Field | Inferred TypeScript Type |
|-----------|--------------------------|
| .text() | string |
| .number() | number |
| .boolean() | boolean |
| .dateTime() | Date |
| .json() | Record<string, unknown> |
| .enum(values) | Union literal type |
| .id() | string |
| .hasOne(Model) | Inferred model type |
| .hasMany(Model) | Array of inferred model type |
| .belongsTo(Model) | Inferred model type |
| .nullable() | T | null |
The MedusaService factory generates a typed service class from DML models:
// Fetch live docs for MedusaService generic
// signature and generated method types
class MyService extends MedusaService({ MyModel }) {}
| Generated Method | Return Type | Purpose |
|-----------------|-------------|---------|
| list(filters, config) | Promise<T[]> | List with filters and pagination |
| retrieve(id, config) | Promise<T> | Get single entity by ID |
| create(data) | Promise<T> | Create one entity |
| update(data) | Promise<T> | Update entity by ID |
| delete(id) | Promise<void> | Delete entity by ID |
| listAndCount(filters, config) | Promise<[T[], number]> | List with total count |
| softDelete(id) | Promise<T> | Soft delete entity |
| restore(id) | Promise<T> | Restore soft-deleted entity |
Custom methods added to the service class are fully typed alongside generated ones.
Medusa resolves dependencies from a typed container. Module services are registered and resolved by key:
| Registration Pattern | Resolution Key | Type |
|---------------------|---------------|------|
| Module service | ModuleRegistrationName.MODULE | Module service interface |
| Custom module service | Defined in module export | Custom service class |
| Remote query | ContainerRegistrationKeys.QUERY | RemoteQueryFunction |
| Logger | ContainerRegistrationKeys.LOGGER | Logger |
Access the container in API routes via req.scope.resolve("key"). In workflow steps, resolve via the StepFunction context. Use typed registration keys for compile-time safety.
| Check | Flag | Effect on Medusa Code |
|-------|------|-----------------------|
| strictNullChecks | Part of strict | Must handle null/undefined explicitly |
| noImplicitAny | Part of strict | All parameters and variables must be typed |
| strictPropertyInitialization | Part of strict | All class properties must be initialized |
| noImplicitReturns | Recommended | All code paths must return a value |
| noUnusedLocals | Recommended | Catch dead code early |
| exactOptionalPropertyTypes | Optional | Distinguish undefined from missing |
.nullable() fields produce T | null — always check before usingretrieve() may throw if entity not found — wrap in try/catch or use list() with filters?: — provide defaults where needed| Utility Type | Use Case in Medusa |
|-------------|-------------------|
| Partial<T> | Update DTOs (only changed fields required) |
| Required<T> | Ensure all fields present in creation |
| Pick<T, Keys> | Select specific fields for API responses |
| Omit<T, Keys> | Exclude internal fields from public DTOs |
| Record<string, T> | Metadata and JSON fields |
| NonNullable<T> | Assert non-null after null check |
| Awaited<T> | Unwrap Promise return types |
| ReturnType<T> | Extract return type from functions |
| Type | Import From | Purpose |
|------|-------------|---------|
| InferTypeOf | @medusajs/framework/types | Infer entity type from DML model |
| MedusaRequest | @medusajs/framework/http | Typed HTTP request in API routes |
| MedusaResponse | @medusajs/framework/http | Typed HTTP response in API routes |
| StepResponse | @medusajs/framework/workflows-sdk | Typed workflow step return |
| MedusaContainer | @medusajs/framework/types | DI container type |
Each custom module should export typed interfaces for its service:
// Fetch live docs for module type export
// conventions and service interface patterns
| File | Purpose |
|------|---------|
| src/modules/<name>/models/ | DML model definitions |
| src/modules/<name>/service.ts | Service extending MedusaService |
| src/modules/<name>/index.ts | Module definition with Module() factory |
| src/modules/<name>/types.ts | Custom types and interfaces (optional) |
Pick/Omit to shape public API types from internal entity typescreate and update operationsAPI routes use Zod schemas for runtime validation. Infer TypeScript types from Zod schemas to avoid duplication:
// Fetch live docs for Zod schema usage in
// Medusa API route validators
import { z } from "zod"
| Pattern | Purpose |
|---------|---------|
| z.infer<typeof schema> | Derive TS type from Zod schema |
| defineMiddlewares | Register Zod validators on routes |
| additionalDataValidator | Extend existing route validation |
InferTypeOf to derive types; never manually duplicate entity types as interfaces; keep the DML model as the single source of truthstrict: true in tsconfig.json; handle all nullable cases explicitly; use NonNullable and type guards instead of non-null assertions (!)any casts on resolved services; define explicit return types on custom service methodsz.infer; type API route handlers with MedusaRequest<T> and MedusaResponseFetch the Medusa TypeScript documentation for exact type imports, generic signatures, and DML inference 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.