medusajs/medusa · error · Error

Step registration requires either sourcePath or workflowId.

Error message

Step registration requires either sourcePath or workflowId. Received: ${JSON.stringify(data)}

What it means

Dev-server validation error from StepHandler.validate: a step resource was registered with neither `sourcePath` nor `workflowId`. The dev server resolves a step's file either directly from its source path or by looking up the parent workflow's registered source; if both are absent it cannot map the step to a file, so registration is rejected.

Source

Thrown at packages/core/utils/src/dev-server/handlers/step-handler.ts:14

import { ResourceEntry, ResourceTypeHandler, StepResourceData } from "../types"

export class StepHandler implements ResourceTypeHandler<StepResourceData> {
  readonly type = "step"

  constructor(private inverseRegistry: Map<string, string[]>) {}

  validate(data: StepResourceData): void {
    if (!data.id) {
      throw new Error(
        `Step registration requires id. Received: ${JSON.stringify(data)}`
      )
    }

    if (!data.sourcePath && !data.workflowId) {
      throw new Error(
        `Step registration requires either sourcePath or workflowId. Received: ${JSON.stringify(
          data
        )}`
      )
    }
  }

  resolveSourcePath(data: StepResourceData): string {
    if (data.sourcePath) {
      return data.sourcePath
    }

    // Look up workflow's source path
    const workflowKey = `workflow:${data.workflowId}`
    const workflowSourcePaths = this.inverseRegistry.get(workflowKey)

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. Provide sourcePath (file path of the step module) — the direct option.
  2. Or provide workflowId of an already-registered workflow so the handler can resolve the step's path from the inverse registry.
  3. If the step belongs to a workflow, make sure the workflow itself is registered before the step.

Example fix

// before
registerDevServerResource({ type: 'step', id: 'price-step' })

// after
registerDevServerResource({
  type: 'step',
  id: 'price-step',
  sourcePath: fileURLToPath(import.meta.url),
})
Defensive patterns

Strategy: validation

Validate before calling

if (!data.sourcePath && !data.workflowId) throw new Error(`Step ${data.id} needs sourcePath or workflowId`)

Type guard

function isResolvableStep(d: { sourcePath?: string; workflowId?: string }): boolean {
  return Boolean(d.sourcePath) || Boolean(d.workflowId)
}

Prevention

When it happens

Trigger: Registering a step with only { type: 'step', id } and nothing else; registration data built from a partial object where both fields were dropped during refactoring.

Common situations: Custom loaders registering steps they discovered without file context; splitting step definitions into helpers imported by workflows so the loader loses track of both the file and the owning workflow.

Related errors


AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27). Data as JSON: /api/errors/4ceba1d304f3aa6e. Report an issue: GitHub.