Skip to main content
Version: Next

Scheduler Service

When writing plugins, you sometimes want to have things running on a schedule, or something similar to cron jobs that are distributed through instances that your backend plugin is running on. We supply a task scheduler for this purpose that is scoped per plugin so that you can create these tasks and orchestrate their execution.

Using the service

The following example shows how to get the scheduler service in your example backend to issue a scheduled task that runs across your instances at a given interval.

import {
coreServices,
createBackendPlugin,
} from '@backstage/backend-plugin-api';

createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
scheduler: coreServices.scheduler,
},
async init({ scheduler }) {
await scheduler.scheduleTask({
frequency: { minutes: 10 },
timeout: { seconds: 30 },
id: 'ping-google',
fn: async () => {
await fetch('http://google.com/ping');
},
});
},
});
},
});

REST API

The scheduler exposes a REST API on top of each plugin's base URL, that lets you inspect and affect the current state of all of that plugin's tasks.

GET <pluginBaseURL>/.backstage/scheduler/v1/tasks

Lists all tasks that the given plugin registered at startup, and their current states. The response shape is as follows:

{
"tasks": [
{
"taskId": "InternalOpenApiDocumentationProvider:refresh",
"pluginId": "catalog",
"scope": "global",
"settings": {
"version": 2,
"cadence": "PT10S",
"initialDelayDuration": "PT10S",
"timeoutAfterDuration": "PT1M"
},
"taskState": {
"status": "idle",
"startsAt": "2025-04-11T20:35:13.418+02:00",
"lastRunEndedAt": "2025-04-11T20:35:03.453+02:00"
},
"workerState": {
"status": "initial-wait"
}
}
]
}

Each task has the following properties:

FieldFormatDescription
taskIdstringA unique (per plugin) ID for the task
pluginIdstringThe plugin where the task is scheduled
scopestringEither local (runs on each worker node with potential overlaps, similar to setInterval), or global (runs on one worker node at a time, without overlaps)
settingsobjectSerialized form of the initial settings passed in when scheduling the task. The only completely fixed well known field is version; the others depend on what version is used
settings.versionstringInternal identifier of the format of the settings object. The format of this object can change completely for each version. This document describes version 2 specifically
settings.cadencestring; ISO durationHow often the task runs. Either the string manual (only runs when manually triggered), or an ISO duration string starting with the letter P, or a cron format string
settings.initialDelayDurationstring; ISO durationHow long workers wait at service startup before starting to look for work, to give the service some time to stabilize, as an ISO duration string (if configured)
settings.timeoutAfterDurationstring; ISO durationHow long after a task starts that it's considered timed out and available for retries
taskStateobjectThe current state of the task (see below for details)
workerStateobjectThe status of the worker responsible for task

The taskState shape depends on whether the task is currently running or not. When running:

FieldFormatOptionalDescription
taskState.statusstringrunning
taskState.startedAtstring; ISO timestampWhen the current task run started
taskState.timesOutAtstring; ISO timestampWhen the current task run will time out if it does not finish before that
taskState.lastRunErrorstring; JSON serialized erroroptionalWhen the task last ran, if it threw an error, this field contains it
taskState.lastRunEndedAtstring; ISO timestampoptionalWhen the task last ran, it ended at this time

When the task is idle:

FieldFormatOptionalDescription
taskState.statusstringidle
taskState.startsAtstring; ISO timestampoptionalWhen the task is scheduled to run next; will not be set if the task uses manual scheduling
taskState.lastRunErrorstring; JSON serialized erroroptionalWhen the task last ran, if it threw an error, this field contains it
taskState.lastRunEndedAtstring; ISO timestampoptionalWhen the task last ran, it ended at this time

The workerState shape is as follows:

FieldDescription
workerState.statusThe status of the worker responsible for task; either initial-wait (right at service startup), running (task is currently running), or idle (task is not running at the moment)

POST <pluginBaseURL>/.backstage/scheduler/v1/tasks/<taskId>/trigger

Schedules the given task ID for immediate execution, instead of waiting for its next scheduled time slot to arrive.

Note that there can still be an additional small delay before a worker discovers that the task is due and actually picks it up. This typically takes less than a second, but it can vary.

The request has no body.

Responds with

  • 200 OK if successful
  • 404 Not Found if there was no such registered task for this plugin
  • 409 Conflict if the task was already in a running state