Skip to main content

JavaScript SDK

The Conductor JavaScript SDK lets you write workers, define workflows as code, and manage workflow executions from your JavaScript or TypeScript application.

Prerequisites
  • Node.js 18 or above
  • A Conductor server to connect to. For quick testing, sign up for free Orkes Developer Edition.

Install the SDK

Initialize a new project:

npm init -y
npm pkg set type=module

Install the SDK and TypeScript tooling:

npm install @io-orkes/conductor-javascript
npm install --save-dev typescript tsx

Connect to Conductor

In Orkes Conductor, an application represents your SDK client and controls what it can access on the cluster. Each application has access keys (a key ID and secret) that the SDK uses to authenticate your requests.

To create an application:

  1. Go to Access Control > Applications from the left menu on your Conductor cluster.
  2. Select + Create application.
  3. Enter the application name.
  4. Select Save.

To retrieve the access key:

  1. Open the application.
  2. In Application roles, enable the Worker role.
  3. In the Access Keys section, select + Create access key to generate a unique Key ID, Key Secret, and Server URL.

The Key Secret is shown only once. So ensure to copy and store it securely.

Set environment variables

The SDK reads your server URL and credentials from environment variables. To make them available in every terminal session, add them to your shell profile (~/.zshrc or ~/.bash_profile):

export CONDUCTOR_SERVER_URL=https://SERVER_URL/api
export CONDUCTOR_AUTH_KEY=your-key-id
export CONDUCTOR_AUTH_SECRET=your-key-secret

Reload your shell profile after adding:

source ~/.zshrc
Note

If you set environment variables using export in a terminal, they only persist for that session. Any new terminal will require you to export them again, which is a common source of connection errors when running workers and workflows in separate terminals.

Initialize the SDK clients

Every application that uses the Conductor JavaScript SDK starts with the same setup:

import { OrkesClients } from "@io-orkes/conductor-javascript";

const clients = await OrkesClients.from();

OrkesClients.from() is an async factory that reads your environment variables automatically; no arguments needed. It returns a configured instance you use to get the specific clients you need:

const executor = clients.getWorkflowClient();  // build, register, run, and manage workflows
const taskClient = clients.getTaskClient(); // inspect and signal individual tasks

Quickstart

This tutorial walks you through creating a workflow with a single worker task and running it end-to-end.

Open your terminal and create a new folder for your project:

mkdir conductor-app
cd conductor-app

Create a new file called quickstart.ts inside that folder and paste the following code:

import {
OrkesClients,
TaskHandler,
worker,
ConductorWorkflow,
simpleTask,
} from "@io-orkes/conductor-javascript";
import type { Task } from "@io-orkes/conductor-javascript";

class Workers {
@worker({ taskDefName: "greet" })
async greet(task: Task) {
const name = task.inputData?.name ?? "World";
return { status: "COMPLETED" as const, outputData: { result: `Hello ${name}` } };
}
}

const main = async () => {
const clients = await OrkesClients.from();
const executor = clients.getWorkflowClient();

const workflow = new ConductorWorkflow(executor, "greetings")
.add(simpleTask("greet_ref", "greet", { name: "${workflow.input.name}" }))
.outputParameters({ result: "${greet_ref.output.result}" });
await workflow.register();

void new Workers(); // instantiating the class triggers the decorators
const handler = new TaskHandler({ client: clients.getClient(), scanForDecorated: true });
await handler.startWorkers();

const run = await workflow.execute({ name: "Conductor" });

console.log(`Result: ${run.output?.result}`);
console.log(`Execution: https://developer.orkescloud.com/execution/${run.workflowId}`);

await handler.stopWorkers();
};

main();

What this does:

  • Define a worker: Registers a greet function as the handler for the greet task type using the @worker decorator.
  • Initialize the SDK: Reads your environment variables and creates the SDK clients.
  • Define and register the workflow: Builds a workflow with one task and registers the workflow definition on the Conductor server.
  • Start the worker: Begins polling for tasks.
  • Run the workflow: Triggers an execution and waits for the result.

Run it:

npx tsx quickstart.ts

Expected output:

Result: Hello Conductor
Execution: https://developer.orkescloud.com/execution/<workflow-id>

Open the execution URL to view the workflow run in the Conductor UI. Select the task, then go to the Output tab to confirm the output is displayed.

Workflow executed using JavaScript SDK

This also registers the workflow definitions on your Conductor cluster. To verify, go to Definitions > Workflow, and confirm that the greetings workflow is created.

Greetings workflow created using JavaScript SDK

That's it. You have successfully run a simple workflow. Next, explore the core concepts to understand how to build your own workflows.

Workers

A worker is a piece of code responsible for executing a task. In Conductor, workers can be implemented in any language and deployed anywhere.

Implement a worker

In JavaScript/TypeScript, workers are class methods decorated with @worker. The decorator registers the method as the handler for a task type. Group related workers in a class and pass the class to TaskHandler with scanForDecorated: true for auto-discovery.

import { worker } from "@io-orkes/conductor-javascript";
import type { Task } from "@io-orkes/conductor-javascript";

class Workers {
@worker({ taskDefName: "greet" })
async greet(task: Task) {
const name = task.inputData?.name ?? "World";
return { status: "COMPLETED" as const, outputData: { result: `Hello ${name}` } };
}
}

The taskDefName option sets the task definition name this method handles.

Access task inputs

Task inputs are available on task.inputData:

class Workers {
@worker({ taskDefName: "process_order" })
async processOrder(task: Task) {
const { orderId, sku, quantity } = task.inputData ?? {};
return { status: "COMPLETED" as const, outputData: { message: `Processing order ${orderId}` } };
}
}

Task context

Use task.retryCount inside a worker to track the current attempt number. It is 0 on first execution and increments with each retry:

class Workers {
@worker({ taskDefName: "charge_payment" })
async chargePayment(task: Task) {
const attempt = task.retryCount ?? 0;
const { amount, accountId } = task.inputData ?? {};
console.log(`Charge attempt ${attempt} for account ${accountId}`);
// ... payment logic ...
return { status: "COMPLETED" as const, outputData: { status: "charged" } };
}
}

Terminal errors

A regular thrown Error triggers the retry policy. NonRetryableException tells Conductor to fail the task immediately regardless of how many retries are configured:

import { NonRetryableException } from "@io-orkes/conductor-javascript";
import type { Task } from "@io-orkes/conductor-javascript";

class Workers {
@worker({ taskDefName: "validate_order" })
async validateOrder(task: Task) {
const { customerId } = task.inputData ?? {};
if (!customerId) {
throw new NonRetryableException("Missing customer ID");
}
return { status: "COMPLETED" as const, outputData: { valid: true } };
}
}

Use it when retrying would never help: invalid input, missing records, unauthorized requests. The task status in the UI will show Failed_with_terminal_error.

Concurrency with concurrency

The @worker decorator accepts a concurrency option (default: 1) that controls how many tasks the worker executes in parallel:

class Workers {
@worker({ taskDefName: "process_order", concurrency: 5 })
async processOrder(task: Task) {
const { orderId } = task.inputData ?? {};
return { status: "COMPLETED" as const, outputData: { message: `order: ${orderId}` } };
}
}

Recommended values:

  • CPU-bound tasks: 1–4
  • I/O-bound tasks: 10–50 (workers spend most time waiting)

Start and stop workers

Workers use a long-poll mechanism to check for tasks. The TaskHandler class manages worker startup and shutdown:

import { TaskHandler, OrkesClients } from "@io-orkes/conductor-javascript";

const clients = await OrkesClients.from();
void new Workers(); // instantiating the class triggers the decorators
const handler = new TaskHandler({ client: clients.getClient(), scanForDecorated: true });

await handler.startWorkers();

// ... application runs ...

await handler.stopWorkers();

Worker design principles

  • Workers are stateless and contain no workflow-specific logic.
  • Each worker executes one task and produces a defined output for a given input.
  • Workers should be idempotent; a task may be rescheduled if it times out.
  • Retry and timeout logic is handled by the Conductor server, not the worker.

Workflows

A workflow is the blueprint that connects tasks into a sequence. It defines which tasks run, in what order, and how outputs from one task become inputs to the next. Workflows handle branching, parallelism, retries, and timeouts; all configured in the workflow definition, not in your worker code.

You can define workflows in the Conductor UI, via the API, or in code using the SDK.

Define a workflow as code

Use ConductorWorkflow to define workflows in TypeScript. The .add() method sets task execution order:

import {
OrkesClients,
ConductorWorkflow,
simpleTask,
} from "@io-orkes/conductor-javascript";

const clients = await OrkesClients.from();
const executor = clients.getWorkflowClient();

const workflow = new ConductorWorkflow(executor, "email_workflow")
.add(simpleTask("get_user_email_ref", "get_user_email", { userid: "${workflow.input.userid}" }))
.add(simpleTask("send_email_ref", "send_email", {
email: "${get_user_email_ref.output.result}",
subject: "Hello from Orkes",
body: "Welcome!"
}))
.outputParameters({ email: "${get_user_email_ref.output.result}" });

await workflow.register();

Use system tasks

System tasks are pre-built tasks available in Conductor without writing a worker.

Wait task: Pauses the workflow until a certain timestamp, duration, or an external signal is received.

import { waitTaskDuration, waitTaskUntil } from "@io-orkes/conductor-javascript";

// Wait for a fixed duration
const waitTwoSec = waitTaskDuration("wait_2_sec", "PT2S");

// Wait until a specific time
const waitTillDate = waitTaskUntil("wait_till_date", "2030-01-31 00:00 UTC");

HTTP task: The HTTP task is used to make calls to remote services exposed over HTTP/HTTPS.

import { httpTask } from "@io-orkes/conductor-javascript";

const httpCall = httpTask("call_api", {
uri: "https://orkes-api-tester.orkesconductor.com/api",
method: "GET",
});

Inline task: Runs ECMA-compliant JavaScript inline.

import { inlineTask } from "@io-orkes/conductor-javascript";

const js = inlineTask("hello_script", {
expression: "function greetings() { return { text: 'hello ' + $.name } } greetings();",
evaluatorType: "graaljs",
inputParameters: { name: "${workflow.input.name}" },
});

Learn more about other task types.

Execute a workflow

Asynchronously: Use when workflows are long-running.

const workflowId = await executor.startWorkflow({
name: "greetings",
version: 1,
input: { name: "Orkes" },
});

Synchronously: Use when workflows complete quickly.

const run = await workflow.execute({ name: "Orkes" });

Manage workflow executions

Get executor from OrkesClients:

const clients = await OrkesClients.from();
const executor = clients.getWorkflowClient();

Get execution status

Retrieves the status of the workflow execution.

const workflow = await executor.getWorkflow(workflowId, true);

When the second argument is true, the response includes all completed and in-progress tasks.

Pause and resume

A paused workflow lets currently running tasks complete but does not schedule new tasks until resumed.

await executor.pause(workflowId);
await executor.resume(workflowId);

Terminate

Stops the workflow immediately and moves it to TERMINATED state.

await executor.terminateWorkflow(workflowId, "Cancelled by user");

Retry a failed workflow

Resumes the workflow from the failed task without restarting from the beginning.

await executor.retryWorkflow(workflowId, false);
// pass true to resume sub-workflows from the failed task

Restart a workflow

Restarts a workflow in a terminal state (COMPLETED, TERMINATED, FAILED) from the beginning.

await executor.restart(workflowId, false);
// pass true to use the latest workflow definition

Search executions

Queries workflow executions by status, type, or other fields.

const results = await executor.search(
0,
10,
'workflowType="greetings" AND status="FAILED"',
"Orkes"
);

Supported query fields: status, correlationId, workflowType, version, startTime.

Test workflows

The Conductor server provides a test endpoint (POST /api/workflow/test) that lets you run a workflow with mocked task outputs, no workers required.

import { WorkflowTestRequest } from "@io-orkes/conductor-javascript";

const testRequest: WorkflowTestRequest = {
name: workflow.getName(),
version: workflow.getVersion(),
input: { name: "Orkes" },
workflowDef: workflow,
taskRefToMockOutput: {
greet_ref: [
{
status: "COMPLETED",
output: { result: "Hello, Orkes" },
},
],
},
};

const run = await executor.testWorkflow(testRequest);
console.assert(run.status === "COMPLETED");

Worker functions are regular async functions and can be tested independently with any testing framework.

Next steps

  • Examples: Browse the full examples directory on GitHub.