Tasks
Nitro tasks allow one-off operations at runtime: named server-side actions such as database migrations, cache cleanup, or content syncing that you can run on demand, on a schedule, or from the CLI.
#Opt-in to the experimental feature
Important
Tasks support is currently experimental. See nitrojs/nitro#1974 for the relevant discussion.
In order to use the tasks API you need to enable the experimental feature flag. Tasks are scanned from your serverDir, which has to be set as well:
import { defineConfig } from "nitro";
export default defineConfig({
serverDir: "./server",
experimental: {
tasks: true
}
})All examples on this page assume this config.
#Define tasks
Tasks can be defined in tasks/[name].ts files inside your serverDir.
Note
Like routes and plugins, tasks are only scanned when serverDir (or scanDirs) is set; no directories are scanned by default.
Nested directories are supported. The task name will be joined with :.
server/
tasks/
cleanup.ts <-- cleanup
db/
migrate.ts <-- db:migrate
nitro.config.tsExample:
import { defineTask } from "nitro/task";
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 optionalnameanddescriptionstring fields used for display in the dev server and CLI.run(required): A function that receives aTaskEventand returns (or resolves to) an object with an optionalresultproperty.
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: ATaskContextobject (may includewaitUntildepending 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 adding a description to a scanned task, or for tasks provided by modules and pointing to custom handler paths.
import { fileURLToPath } from "node:url";
import { defineConfig } from "nitro";
export default defineConfig({
serverDir: "./server",
experimental: {
tasks: true
},
tasks: {
// Describe a task scanned from `server/tasks/db/migrate.ts`
"db:migrate": {
description: "Run database migrations"
},
// Register a task from a custom location
"db:seed": {
handler: fileURLToPath(new URL("scripts/seed.ts", import.meta.url)),
description: "Seed the database"
}
}
})Warning
Unlike route handlers, a task handler is imported as-is and is not resolved against the project root, so a path such as "./scripts/seed.ts" will fail to resolve at build time. Use an absolute path (for example via fileURLToPath(new URL(..., import.meta.url))) or a package name.
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.
import { defineConfig } from "nitro";
export default defineConfig({
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.
Tip
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()).
Note
Prefer scheduledTasks when your deployment target supports it (see platform support). Nitro wires up the platform's native scheduler for you. If your platform is not supported, you can still trigger tasks from an external scheduler by calling runTask from an authenticated HTTP endpoint.
#Platform support
dev,node_server,node_cluster,node_middleware,bunanddeno_serverpresets are supported with the croner engine.cloudflare_moduleandcloudflare_pagespresets have native integration with Cron Triggers. Nitro automatically generates the cron triggers in the wrangler config at build time - no manual wrangler setup required.vercelpreset has native integration with Vercel Cron Jobs. Nitro automatically generates the cron job configuration at build time - no manualvercel.jsonsetup required. You can secure cron endpoints by setting theCRON_SECRETenvironment 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.
import { defineTask } from "nitro/task";
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:
import { defineHandler } from "nitro";
import { runTask } from "nitro/task";
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.
import { defineTask } from "nitro/task";
export default defineTask({
meta: {
name: "echo:payload",
description: "Returns the provided payload",
},
run({ payload, context }) {
console.log("Running echo task...");
return { result: payload };
},
});Note
The JSON payload included in the body will overwrite the keys present in the query params.
#Using CLI
Important
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
Task runs are deduplicated by task name: each task can have at most one running instance per server instance. Calling runTask for a task that is already running does not start a new run: all concurrent callers, even those passing different payloads, share the result of the first in-flight run.