This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Dapr Jobs JavaScript SDK

Schedule, retrieve, delete, and handle Dapr Jobs using the JavaScript SDK

Use the Dapr JavaScript SDK to schedule future operations in Node.js applications. Jobs can run once, at a fixed interval, or on a calendar-based schedule, with an optional payload and failure policy.

This documentation targets @dapr/dapr 3.19.0 or later and Dapr runtime 1.16 or later, with a running Scheduler service.

The SDK exposes Jobs through the existing client and server:

APIPurpose
DaprClient.jobs.schedule(name, options)Create a job or explicitly overwrite an existing job.
DaprClient.jobs.get(name)Retrieve a job’s schedule, payload, and other settings.
DaprClient.jobs.delete(name)Delete a job by name.
DaprServer.jobs.listen(name, callback)Register an asynchronous handler for a named job.
CronExpressionBuilderBuild a six-field cron expression with optional time zone.

Both HTTP and gRPC support job management and JSON callbacks. Opaque binary payloads require gRPC for scheduling, retrieval, and callbacks. The SDK automatically selects the stable or Alpha1 Jobs API for the connected runtime.

These are the JavaScript SDK’s Jobs APIs in this package version. It does not expose job listing, bulk deletion, deletion by prefix, or per-operation cancellation options. Do not assume that all management methods in another language SDK are available in JavaScript.

To get started, follow the Jobs how-to guide. Read the client and callback usage guidance for options, serialization, compatibility, and errors, or the cron builder guide for calendar-based scheduling.

See the Jobs overview and Jobs API reference for the underlying building block.

1 - How to: Author and manage Dapr Jobs using the JavaScript SDK

Learn how to schedule one-time and recurring jobs and handle their callbacks using JavaScript

Let’s register an endpoint to receive a job notification, then schedule the job from the same application. This guide uses DaprServer.jobs for callbacks and its embedded DaprClient.jobs for job management. There is no separate Jobs client package to install.

Prerequisites

In your application directory, initialize an npm project if needed and install the SDK:

npm init -y
npm install @dapr/dapr@3.19.0

The examples use JavaScript ES modules in a .mjs file, so no TypeScript compiler is required. The same APIs are available in TypeScript.

Register a handler and schedule a recurring job

Save the following as app.mjs:

import { CommunicationProtocolEnum, DaprServer } from "@dapr/dapr";

const server = new DaprServer({
  serverHost: "127.0.0.1",
  serverPort: "50002",
  communicationProtocol: CommunicationProtocolEnum.GRPC,
  clientOptions: {
    daprHost: "127.0.0.1",
    daprPort: "50001",
    communicationProtocol: CommunicationProtocolEnum.GRPC,
  },
});

const jobName = "daily-report";

try {
  await server.jobs.listen(jobName, async (data) => {
    console.log(`Received ${jobName}:`, data);
  });

  await server.start();

  await server.client.jobs.schedule(jobName, {
    schedule: "@every 10s",
    repeats: 3,
    data: { report: "daily", format: "csv" },
    failurePolicy: { type: "constant", interval: "1s", maxRetries: 3 },
    overwrite: true,
  });

  const job = await server.client.jobs.get(jobName);
  console.log("Scheduled:", job.name, job.schedule, job.data);
  console.log("Waiting for callbacks. Press Ctrl+C to stop.");

  await new Promise((resolve) => {
    process.once("SIGINT", resolve);
    process.once("SIGTERM", resolve);
  });
} finally {
  try {
    await server.stop();
  } finally {
    await server.client.stop();
  }
}

Start the application and sidecar:

dapr run --app-id jobs-example --app-port 50002 --app-protocol grpc --dapr-grpc-port 50001 --dapr-http-port 3500 -- node app.mjs

The application listens on port 50002; its client sends management requests to the sidecar on port 50001. The job triggers three times at ten-second intervals. The callback receives the payload itself, not a job envelope or the job name. Capture the name in the handler’s closure if needed.

Register handlers before calling server.start(). Await all work in the handler; throw an error or reject its promise when processing fails so Dapr can apply the job’s failure policy. Delivery is at least once, so make handlers idempotent.

The example deliberately uses overwrite: true so it can be rerun while a job with the same name still exists. Without this option, scheduling an existing name fails. Overwriting replaces the existing job, including its timing and repeat settings; do not blindly reset a production schedule whenever a replica starts.

Jobs belong to the scheduling sidecar’s application ID and namespace. Callbacks go to that application, not to an arbitrary target application. Register the handler on every replica that can receive a callback. A handler can use other Dapr APIs to invoke another service or publish an event.

Stopping the Node.js server does not delete jobs stored in the Scheduler. The example also stops the embedded client explicitly because server.stop() only stops the callback server.

Use HTTP instead

For JSON payloads, you can use HTTP for both management requests and callbacks. Change the server’s communicationProtocol to CommunicationProtocolEnum.HTTP, and change its clientOptions to:

clientOptions: {
  daprHost: "127.0.0.1",
  daprPort: "3500",
  communicationProtocol: CommunicationProtocolEnum.HTTP,
},

Run it with the matching app protocol:

dapr run --app-id jobs-example --app-port 50002 --app-protocol http --dapr-grpc-port 50001 --dapr-http-port 3500 -- node app.mjs

The SDK registers the HTTP callback route POST /job/:name; you do not need to implement this route yourself. Client and callback protocols can also differ. See the protocol guidance.

Schedule a one-time job

For a one-time job, supply dueTime without schedule:

await server.client.jobs.schedule("send-notification", {
  dueTime: "30s",
  data: { recipientId: "customer-123" },
});

To use an absolute time, pass an RFC3339 timestamp:

const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000);

await server.client.jobs.schedule("send-notification-tomorrow", {
  dueTime: tomorrow.toISOString(),
  data: { recipientId: "customer-123" },
});

These snippets reuse the running server from the example. To receive these additional jobs, register a matching server.jobs.listen(...) handler for each name before starting the server. The remaining scheduling examples follow the same pattern.

Limit a recurring job’s lifetime

Combine schedule with dueTime to delay when a recurring job becomes active. Use repeats to limit the number of scheduled triggers, ttl to set expiration, or both:

await server.client.jobs.schedule("hourly-cleanup", {
  schedule: "@every 1h",
  dueTime: "10m",
  repeats: 5,
  ttl: "6h",
});

Omitting repeats leaves a recurring job running until it expires or is deleted. Relative dueTime and ttl values are calculated from job creation, not from the previous callback. See the option reference for accepted time formats.

For calendar-based scheduling, use a six-field cron string or a CronExpressionBuilder:

await server.client.jobs.schedule("morning-report", {
  schedule: "CRON_TZ=America/New_York 0 30 9 * * 1-5",
  data: { report: "daily" },
});

Retrieve or delete a job

Retrieve the stored configuration by name:

const job = await server.client.jobs.get("hourly-cleanup");
console.log(job.name, job.schedule, job.dueTime, job.ttl, job.repeats);
console.log(job.data, job.failurePolicy);

To stop future triggers, delete the job:

await server.client.jobs.delete("hourly-cleanup");

Deletion does not cancel callback work that is already running. It also does not unregister the application’s in-memory handler. To change a stored job instead, call schedule with its name, the replacement options, and overwrite: true.

Next steps

Read the client and callback usage guidance for payloads, retries, validation, and runtime compatibility, or the cron builder guide for fluent scheduling.

2 - Build cron schedules using the JavaScript SDK

Use raw cron expressions, descriptors, time zones, and CronExpressionBuilder to schedule Dapr Jobs

Use a cron schedule for calendar-based jobs, such as a report at 09:30 every weekday. The JavaScript SDK accepts a raw schedule string or a CronExpressionBuilder directly in JobOptions.schedule. These examples target @dapr/dapr 3.19.0 or later.

Provide a schedule string

Dapr cron expressions use six fields, including seconds, not the five fields used by many Unix cron tools:

second minute hour dayOfMonth month dayOfWeek

For example, "0 30 9 * * 1-5" runs at 09:30 on weekdays. Sunday is 0 and Saturday is 6.

await client.jobs.schedule("weekday-report", {
  schedule: "0 30 9 * * 1-5",
  data: { report: "daily" },
});

The snippet assumes a configured DaprClient. Register a matching handler in the receiving application as shown in the how-to guide.

You can also pass runtime-supported descriptors directly:

DescriptorSchedule
@every 5mA fixed five-minute interval.
@hourlyThe beginning of every hour.
@daily or @midnightMidnight each day.
@weeklyMidnight on Sunday.
@monthlyMidnight on the first day of each month.
@yearly or @annuallyMidnight on January 1.

Use dueTime without schedule for a one-time job. The runtime validates raw schedule strings; the SDK does not parse them locally.

Use CronExpressionBuilder

The fluent builder follows the .NET SDK’s on, through, each, and every model, but uses string period names instead of .NET enums:

import { CronExpressionBuilder } from "@dapr/dapr";

const schedule = new CronExpressionBuilder()
  .on("second", 0)
  .on("minute", 30)
  .on("hour", 9)
  .through("dayOfWeek", 1, 5)
  .withTimeZone("America/New_York");

console.log(schedule.toString());
// CRON_TZ=America/New_York 0 30 9 * * 1-5

await client.jobs.schedule("weekday-report", {
  schedule,
  data: { report: "daily" },
});

All fields initially contain *. An untouched builder produces "* * * * * *", which matches every second, not once per minute. Set the seconds field explicitly when you want minute-level or less frequent execution.

Periods and bounds

The exported TypeScript CronPeriod type accepts these names:

PeriodInteger values
second0-59
minute0-59
hour0-23
dayOfMonth1-31
month1-12
dayOfWeek0-6, starting with Sunday

Builder operations

MethodResult
on(period, ...values)Select values, such as on("hour", 9, 17). Values are sorted and deduplicated. At least one is required.
through(period, from, to)Select an inclusive range, such as through("dayOfWeek", 1, 5). from must be less than to.
each(period)Reset a field to *.
every(period, interval)Set a step expression, such as every("minute", 15) producing */15.
withTimeZone(timeZone)Prefix the expression with CRON_TZ= using a validated IANA time zone.
toString()Return the six-field expression as a string, including its time zone if set.

Each fluent method mutates and returns the same builder. A later call for a period replaces that field; it does not append another constraint. For example, on("hour", 9).on("hour", 17) selects only 17, while on("hour", 9, 17) selects both.

Values and range endpoints must be integers within the period’s bounds. A step must be a positive integer no larger than the number of values in that field: up to 60 for seconds or minutes, 24 for hours, 31 for days of the month, 12 for months, and 7 for days of the week. Invalid fields or values throw RangeError.

Steps are cron field steps, not elapsed-time intervals. For example, every("hour", 2) selects every second hour of the day; it does not mean two hours after registration. Use "@every 2h" for an elapsed interval.

toString() can be used independently of the Jobs client. Passing the builder to schedule takes its current string value; mutating the builder afterward does not update an already stored job. Reschedule with overwrite: true to change the stored schedule.

Specify a time zone

By default, Dapr evaluates a cron schedule in the Scheduler service’s local time zone, not necessarily the Node.js process’s time zone. Use an explicit IANA zone for a predictable wall-clock schedule:

const schedule = new CronExpressionBuilder()
  .on("second", 0)
  .on("minute", 0)
  .on("hour", 9)
  .withTimeZone("Europe/Rome");

The builder validates the time zone using Node.js Intl.DateTimeFormat. Empty, whitespace-containing, or unrecognized names are rejected. A subsequent withTimeZone call replaces the previous zone.

You can also pass the runtime’s time zone prefix in a raw string, for example "CRON_TZ=Europe/Rome 0 0 9 * * *" or "CRON_TZ=Europe/Rome @daily". Use "UTC" when daylight saving changes are not desired. With a regional zone, Dapr evaluates the local wall-clock schedule across daylight saving transitions.

Do not combine a time zone prefix with @every: fixed-duration intervals do not have a wall-clock time zone and the runtime rejects that combination. See the Jobs API reference for runtime schedule syntax.

3 - JavaScript Jobs client and callback usage

Configure the JavaScript Jobs client and understand scheduling options, payloads, failure policies, and errors

Jobs is part of DaprClient and DaprServer in @dapr/dapr 3.19.0 or later. Use client.jobs to manage scheduled jobs and server.jobs to register callbacks. The how-to guide contains a complete runnable application.

Client configuration and lifetime

Create a long-lived DaprClient rather than a new client for every operation. Jobs uses the client’s existing sidecar connection and API token:

import { CommunicationProtocolEnum, DaprClient } from "@dapr/dapr";

const client = new DaprClient({
  daprHost: "127.0.0.1",
  daprPort: "50001",
  communicationProtocol: CommunicationProtocolEnum.GRPC,
  daprApiToken: process.env.DAPR_API_TOKEN,
});

try {
  await client.start();
  await client.jobs.schedule("cleanup", { dueTime: "1h" });
  const job = await client.jobs.get("cleanup");
  console.log(job);
  await client.jobs.delete("cleanup");
} finally {
  await client.stop();
}

For HTTP, use CommunicationProtocolEnum.HTTP and the sidecar’s HTTP port, normally "3500". Client-only management does not require an app listener, but the target application must expose its callback server when the job fires.

When using DaprServer, configure its embedded client through clientOptions and reuse server.client.jobs. server.start() starts both the callback server and the embedded client. server.stop() stops only the server; also call server.client.stop() during application shutdown. Neither operation deletes stored jobs.

Client protocol and callback protocol

The two connections are configured independently:

ConnectionSDK settingDapr CLI setting
Application to sidecarDaprClient.communicationProtocol, or DaprServer.clientOptions.communicationProtocol, with the corresponding daprPort--dapr-http-port or --dapr-grpc-port
Sidecar to applicationDaprServer.communicationProtocol and serverPortMatching --app-protocol and --app-port

JSON payloads support any combination of HTTP and gRPC. For arbitrary bytes, use gRPC on both connections. Selecting gRPC for the client alone does not change the sidecar’s callback protocol.

Runtime compatibility and API selection

Jobs requires Dapr runtime 1.16 or later and a running Scheduler service. Before making a Jobs request, the SDK reads the sidecar’s metadata to select the API:

Runtime versionHTTP API prefixgRPC management methods
1.16 and 1.17v1.0-alpha1ScheduleJobAlpha1, GetJobAlpha1, DeleteJobAlpha1
1.18 prereleasesv1.0-alpha1Alpha1 methods
1.18 stable and later releasesv1.0ScheduleJob, GetJob, DeleteJob

Prereleases after 1.18 and the edge runtime identifier select the stable API. Both stable and Alpha1 gRPC callback services are registered by DaprServer.

The SDK caches successful API selection per client. Metadata lookup failures, missing or unrecognized versions, and unsupported runtime versions reject the operation rather than guessing an API. A subsequent call can retry a failed metadata lookup. A Jobs request that fails is not automatically replayed against a different API version.

The SDK package version and runtime version are separate requirements: using SDK 3.19.0 does not require runtime 1.19 merely to use Jobs.

Scheduling options

schedule(name, options) returns Promise<void>. Supply at least one of schedule or dueTime; both may be supplied together.

OptionTypeBehavior
schedulestring or CronExpressionBuilderA six-field cron expression, a descriptor such as @daily, or an interval such as @every 5m. The builder is converted to a string.
dueTimestringWhen the job becomes active, or the trigger time for a one-time job without schedule.
ttlstringJob expiration. Relative values are calculated from job creation.
repeatsnumberLimits scheduled triggers. Omit for unlimited recurrence; zero is passed to the runtime, not converted to an omitted value.
dataJSON value or Uint8ArrayOptional callback payload. Node.js Buffer is also accepted by gRPC.
overwritebooleanDefaults to false. Set to true to replace an existing job with the same name.
failurePolicyJobFailurePolicyControls retries when callback processing fails. Omit to use the runtime default.

dueTime and ttl accept RFC3339 timestamps, Go duration strings such as "30s" or "1h30m", and non-repeating ISO8601 durations such as "PT30M". Pass strings, not JavaScript Date objects. For an absolute time, use date.toISOString() or an RFC3339 timestamp with an explicit offset.

The runtime validates raw cron expressions, time strings, and scheduling combinations. Jobs is a durable scheduler, not a precision timer: a trigger will not arrive before its due time, but may arrive later.

Job names and input validation

Names are case-sensitive. Pass a plain name, not a URL-encoded value; the HTTP client encodes the path segment.

The SDK rejects empty names, surrounding whitespace, . and .., control characters, and the characters /, \, ?, #, %, and |. Internal spaces are supported. These SDK restrictions also apply with a newer runtime that allows additional characters, such as |.

The SDK also validates:

  • schedule, dueTime, and ttl, when present, must be nonempty strings after converting a builder.
  • repeats and failurePolicy.maxRetries must be integers between 0 and 4294967295.
  • overwrite, when present, must be a boolean.
  • Payloads and failure policies must have supported values.

Invalid options reject the asynchronous operation before a Jobs request is sent. The cron builder performs additional local validation of its fields.

Retrieving and deleting jobs

get(name) returns Promise<Job> containing name and the optional schedule, dueTime, ttl, repeats, data, and failurePolicy properties. The SDK exports the related types for TypeScript callers:

import type { Job, JobFailurePolicy, JobOptions } from "@dapr/dapr";

const failurePolicy: JobFailurePolicy = {
  type: "constant",
  interval: "1s",
  maxRetries: 3,
};

const options: JobOptions = { dueTime: "1h", failurePolicy };
await client.jobs.schedule("cleanup", options);

const job: Job = await client.jobs.get("cleanup");
console.log(job.name, job.failurePolicy);

JobOptions describes scheduling input, and Job describes retrieved configuration. overwrite is a scheduling option, not a returned job property. Returned timing strings use the runtime’s representation, which may differ from the input, and a builder’s schedule is returned as a string.

Retrieval returns configuration, not execution history, a next-run calculation, or a count of completed callbacks. A missing job causes get to reject, rather than returning undefined. delete(name) returns Promise<void>; deleting an already absent job is accepted by supported runtimes, while transport and service errors still propagate.

Payload serialization

JSON payloads

Both client transports and both callback protocols support JSON values: plain objects, arrays, strings, finite numbers, booleans, and null. Pass the value directly rather than manually JSON-stringifying it:

await client.jobs.schedule("create-report", {
  dueTime: "30s",
  data: { reportId: "report-123", includeArchived: false },
});

get(name).data and the callback argument expose the decoded value. Values such as false, 0, "", and null are preserved rather than treated as missing payloads.

The SDK rejects non-JSON data such as Date instances, BigInt, non-finite numbers, nested undefined values, and circular references. Convert such values to a JSON-compatible representation first.

Jobs uses its own payload encoding, not the client’s custom serializer. JSON payloads are packed as google.protobuf.Value for gRPC; the decoder also understands google.protobuf.Struct payloads from other clients. You do not need to construct protobuf envelopes yourself.

Omitting data is not identical across transports. HTTP scheduling sends JSON null for compatibility with older runtimes; gRPC leaves the payload absent. Do not rely on distinguishing an omitted payload from null across protocols.

Opaque binary payloads

Use gRPC to schedule and retrieve raw Uint8Array or Node.js Buffer payloads, including empty buffers:

const data = Buffer.from([0x00, 0x80, 0xff]);

await client.jobs.schedule("process-bytes", {
  dueTime: "1h",
  data,
});

const job = await client.jobs.get("process-bytes");
console.log(Buffer.isBuffer(job.data)); // true

For this example, configure client with gRPC. Use a gRPC DaprServer and --app-protocol grpc to receive the same bytes in a callback. Dapr’s HTTP app channel treats job callback bodies as JSON and is not suitable for arbitrary bytes.

HTTP scheduling rejects byte payloads rather than converting them to JSON. Opaque gRPC payloads are returned as Node.js buffers. The SDK uses the dapr.io/schedule/jobpayload type URL also used by the .NET Jobs SDK; it does not JSON-serialize or text-encode these bytes. For cross-language interoperability, agree on whether the payload is protobuf-wrapped JSON or raw bytes and decode accordingly.

Failure policies

The SDK accepts the following policies:

PolicyBehavior
OmittedLeave Dapr’s default policy in control. The runtime default retries up to three times with a one-second delay.
{ type: "drop" }Do not retry a failed delivery.
{ type: "constant", interval: "1s", maxRetries: 3 }Retry with a constant delay, up to the specified number of retries after the initial attempt.

For a constant policy, omitting maxRetries allows unlimited retries. Setting maxRetries: 0 disables retries. Omitting interval, or setting it to "0s", requests immediate retries.

Unlike dueTime and ttl, the retry interval uses protobuf duration JSON syntax: seconds with an s suffix and up to nine fractional digits. Use "0.2s" for 200 milliseconds, "60s" for a minute, or "0.000000001s" for a nanosecond. Go-style strings such as "200ms" and "1m" are rejected here, as are negative durations.

await client.jobs.schedule("retryable-task", {
  dueTime: "30s",
  failurePolicy: {
    type: "constant",
    interval: "0.5s",
    maxRetries: 2,
  },
});

This policy permits the initial attempt plus two retries. repeats controls scheduled triggers; it is not a retry limit.

Callback behavior

server.jobs.listen(name, callback) returns Promise<void> and registers an asynchronous (data) => Promise<void> handler. Registration does not schedule a job. Register before starting the server, using exactly the same name as the scheduled job.

The SDK waits for the handler’s promise before acknowledging delivery. A resolved handler means success; its return value is not a response payload or retry instruction. Throw or reject to signal failure. Do not start unawaited background work and return early, or catch processing errors without propagating them if you want Dapr to retry.

Duplicate registration for a name rejects instead of replacing the existing handler. The SDK does not provide a handler removal API.

SituationHTTP callback responsegRPC callback response
Handler completes200Successful JobEventResponse
No matching handler404NotFound
Handler throws or rejects500Internal

Missing handlers and failed handlers are logged and reported as errors, not acknowledged as successful deliveries. Design handlers for at-least-once delivery and use application-level deduplication when processing has non-idempotent side effects.

Error handling

All Jobs methods return promises. Await them so validation errors, metadata failures, sidecar errors, and Scheduler errors reach your application’s error handling.

Input validation can produce TypeError or RangeError; malformed runtime responses also reject. Remote failures propagate through the selected transport. HTTP failures include status and response details in the error message; gRPC failures use the gRPC transport’s error information. There is no Jobs-specific exception class shared across transports.

Do not assume an unsuccessful scheduling request proves that nothing was stored: a response can be lost after the server accepts a request. Reconcile by name before retrying, and choose overwrite deliberately. Failure policies govern callback delivery, not retries of client management requests.

These Jobs methods do not accept an AbortSignal, timeout argument, or cancellation token. Rejecting a local wait does not cancel a job; use delete(name) to remove its future schedule.