payloadcms/payload · error · TaskError

Inline task with ID ${taskID} does not have a valid handler.

Error message

Inline task with ID ${taskID} does not have a valid handler.

What it means

Thrown when an inline task's `task` argument is not a function (or is falsy). The runner assigns `runner = task` for inline tasks, then validates `typeof runner === 'function'`; a non-function inline handler is a programming error in the calling workflow code.

Source

Thrown at packages/payload/src/queues/operations/runJobs/runJob/getRunTaskFunction.ts:114

        } else if (typeof finalRetriesConfig?.shouldRestore === 'function') {
          shouldRestore = await finalRetriesConfig.shouldRestore({
            input,
            job,
            req,
            taskStatus,
          })
        }
        if (shouldRestore) {
          return taskStatus.output
        }
      }

      const runner = isInline
        ? (task as TaskHandler<TaskSlug>)
        : await getTaskHandlerFromConfig(taskConfig)

      if (!runner || typeof runner !== 'function') {
        throw new TaskError({
          executedAt,
          input,
          job,
          message: isInline
            ? `Inline task with ID ${taskID} does not have a valid handler.`
            : `Task with slug ${taskSlug} in workflow ${job.workflowSlug} does not have a valid handler.`,
          parent,
          retriesConfig: finalRetriesConfig,
          taskConfig,
          taskID,
          taskSlug,
          taskStatus,
          workflowConfig,
        })
      }

      let output: TaskHandlerResult<string>['output']

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Pass a function as the `task` argument for inline tasks.
  2. Verify the handler import: `typeof task === 'function'` before queueing.
  3. If you meant a configured task, use the task slug path instead of an inline task.

Example fix

// before
await req.tasks('step', { task: MyTaskConfig /* object, not fn */ })

// after
await req.tasks('step', { task: myTaskHandler })
Defensive patterns

Strategy: type-guard

Validate before calling

function isTaskHandler(value: unknown): value is (...args: any[]) => any {
  return typeof value === 'function'
}

if (!isTaskHandler(task)) {
  throw new Error('inline task handler must be a function')
}

await req.tasks('step', { task })

Type guard

type TaskHandler = (args: any) => Promise<any> | any

function isInlineTaskHandler(value: unknown): value is TaskHandler {
  return typeof value === 'function'
}

if (!isInlineTaskHandler(task)) {
  throw new TypeError('Expected a function for inline task')
}

Try / catch

try {
  await req.tasks('step', { task })
} catch (err) {
  if (err instanceof TaskError && err.message.includes('does not have a valid handler')) {
    // replace the inline task arg with a real function
  } else throw err
}

Prevention

When it happens

Trigger: Calling the inline task runner (`req.tasks` / runTask with an inline task) and passing `task` as undefined, an object, or a non-function import — e.g. forgetting to default-export the handler, or passing a config object instead of the function.

Common situations: Importing a handler object instead of its function member; passing `task: undefined` because of an optional that was never set; refactor that changed the inline task signature.

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/c3a3c41e54c4922f. Report an issue: GitHub.