payloadcms/payload · error · TaskError

Task with slug ${taskSlug} in workflow ${job.workflowSlug} d

Error message

Task with slug ${taskSlug} in workflow ${job.workflowSlug} does not have a valid handler.

What it means

Thrown when a configured (non-inline) task's handler cannot be resolved to a function. The runner calls `getTaskHandlerFromConfig(taskConfig)`; if that returns falsy or a non-function, the task has no executable handler — typically because neither a `handler` function nor a resolvable `handler.path` was defined.

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. Add `handler: async (args) => {...}` to the task config.
  2. Or set `handler: { path: './tasks/foo.js' }` and ensure that module has a default or named export.
  3. Verify `getTaskHandlerFromConfig` can resolve it by importing the module manually in a test.

Example fix

// before
tasks: [{ slug: 'resize', inputSchema, /* no handler */ }]

// after
tasks: [{ slug: 'resize', inputSchema, handler: async ({ input }) => { /* ... */ } }]
Defensive patterns

Strategy: validation

Validate before calling

import type { TaskConfig } from 'payload'

function assertTaskHasHandler(task: TaskConfig): void {
  const hasFn = typeof task.handler === 'function'
  const hasPath =
    !!task.handler && typeof task.handler === 'object' && 'path' in (task.handler as object)
  if (!hasFn && !hasPath) {
    throw new Error(`Task "${task.slug}" has no handler function or path`)
  }
}

(tasks ?? []).forEach(assertTaskHasHandler)

Type guard

function hasHandler(task: { handler?: unknown }): boolean {
  return typeof task.handler === 'function' ||
    (!!task.handler && typeof task.handler === 'object' && 'path' in (task.handler as object))
}

if (!hasHandler(taskConfig)) throw new Error('task missing handler')

Try / catch

try {
  await runJobs({ req })
} catch (err) {
  if (err instanceof Error && err.message.includes('does not have a valid handler')) {
    // add handler to the task config or fix the handler.path export
  } else throw err
}

Prevention

When it happens

Trigger: A task in `jobs.tasks` / a workflow's tasks has no `handler` and no valid `handler.path`, or the path resolves to a module without a default/named export.

Common situations: Defining a task config with only a slug and input/output schema but forgetting the handler; handler path string pointing to a file that has no matching export; refactor removed the handler function.

Related errors


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