Tasks

Nitro tasks allow on-off operations in runtime.

Opt-in to the experimental feature

Tasks support is currently experimental. See nitrojs/nitro#1974 for the relevant discussion.

In order to use the tasks API you need to enable experimental feature flag.

nitro.config.ts
import { defineNitroConfig } from "nitro/config";

export default defineNitroConfig({
  experimental: {
    tasks: true
  }
})

Define tasks

Tasks can be defined in tasks/[name].ts files.

Nested directories are supported. The task name will be joined with :. (Example: tasks/db/migrate.ts task name will be db:migrate)

Example:

tasks/db/migrate.ts
export default defineTask({
  meta: {
    name: "db:migrate",
    description: "Run database migrations",
  },
  run({ payload, context }) {
    console.log("Running DB migration task...");
    return { result: "Success" };
  },
});

Task interface

The defineTask helper accepts an object with the following properties:

  • meta (optional): An object with optional name and description string fields used for display in the dev server and CLI.
  • run (required): A function that receives a TaskEvent and returns (or resolves to) an object with an optional result property.
interface Task<RT = unknown> {
  meta?: { name?: string; description?: string };
  run(event: TaskEvent): { result?: RT } | Promise<{ result?: RT }>;
}

TaskEvent

The run function receives a TaskEvent object with the following properties:

  • name: The name of the task being executed.
  • payload: An object (Record<string, unknown>) containing any data passed to the task.
  • context: A TaskContext object (may include waitUntil depending on the runtime).
interface TaskEvent {
  name: string;
  payload: TaskPayload;
  context: TaskContext;
}

Registering tasks via config

In addition to file-based scanning, tasks can be registered directly in the Nitro config. This is useful for tasks provided by modules or pointing to custom handler paths.

nitro.config.ts
import { defineNitroConfig } from "nitro/config";

export default defineNitroConfig({
  experimental: {
    tasks: true
  },
  tasks: {
    "db:migrate": {
      handler: "./tasks/custom-migrate.ts",
      description: "Run database migrations"
    }
  }
})

If a task is both scanned from the tasks/ directory and defined in the config, the config-defined handler takes precedence.

Scheduled tasks

You can define scheduled tasks using Nitro configuration to automatically run after each period of time.

nitro.config.ts
import { defineNitroConfig } from "nitro/config";

export default defineNitroConfig({
  scheduledTasks: {
    // Run `cms:update` task every minute
    '* * * * *': ['cms:update'],
    // Run a single task (string shorthand)
    '0 * * * *': 'db:cleanup'
  }
})

The scheduledTasks config maps cron expressions to either a single task name (string) or an array of task names. When multiple tasks are assigned to the same cron expression, they run in parallel.

You can use crontab.guru to easily generate and understand cron tab patterns.

When a scheduled task runs, it automatically receives a payload with scheduledTime set to the current timestamp (Date.now()).

Platform support

  • dev, node_server, node_cluster, node_middleware, bun and deno_server presets are supported with the croner engine.
  • cloudflare_module and cloudflare_pages presets have native integration with Cron Triggers. Nitro automatically generates the cron triggers in the wrangler config at build time - no manual wrangler setup required.
  • vercel preset has native integration with Vercel Cron Jobs. Nitro automatically generates the cron job configuration at build time - no manual vercel.json setup required. You can secure cron endpoints by setting the CRON_SECRET environment variable.
  • More presets (with native primitives support) are planned to be supported!

waitUntil

When running background tasks, you might want to make sure the server or worker waits until the task is done.

An optional context.waitUntil function might be available depending on the runtime.

export default defineTask({
  run({ context }) {
    const promise = fetch(...)
    context.waitUntil?.(promise);
    await promise;
    return { result: "Success" };
  },
});

Programmatically run tasks

To manually run tasks, you can use runTask(name, { payload?, context? }) utility from nitro/task.

Example:

api/migrate.ts
import { defineHandler } from "nitro";

export default defineHandler(async (event) => {
  // IMPORTANT: Authenticate user and validate payload!
  const payload = Object.fromEntries(event.url.searchParams);
  const { result } = await runTask("db:migrate", { payload });

  return { result };
});

Error handling

runTask throws an HTTP error if:

  • The task does not exist (status 404).
  • The task has no handler implementation (status 501).

Any errors thrown inside the task's run function will propagate to the caller.

Run tasks with dev server

Nitro's built-in dev server exposes tasks to be easily executed without programmatic usage.

Using API routes

/_nitro/tasks

This endpoint returns a list of available task names and their meta.

// [GET] /_nitro/tasks
{
  "tasks": {
    "db:migrate": {
      "description": "Run database migrations"
    },
     "cms:update": {
      "description": "Update CMS content"
    }
  },
  "scheduledTasks": [
    {
      "cron": "* * * * *",
      "tasks": [
        "cms:update"
      ]
    }
  ]
}

/_nitro/tasks/:name

This endpoint executes a task. You can provide a payload using both query parameters and body JSON payload. The payload sent in the JSON body payload must be under the "payload" property.

export default defineTask({
  meta: {
    name: "echo:payload",
    description: "Returns the provided payload",
  },
  run({ payload, context }) {
    console.log("Running echo task...");
    return { result: payload };
  },
});
The JSON payload included in the body will overwrite the keys present in the query params.

Using CLI

It is only possible to run these commands while the dev server is running. You should run them in a second terminal.

List tasks

nitro task list

Run a task

nitro task run db:migrate --payload "{}"

The --payload flag accepts a JSON string that will be parsed and passed to the task. If the value is not a valid JSON object, the task runs without a payload.

Notes

Concurrency

Each task can have one running instance. Calling a task of same name multiple times in parallel, results in calling it once and all callers will get the same return value.

Nitro tasks can be running multiple times and in parallel.