Skip to main content

Workflow Clients in TypeScript

@temporalio/client NPM API reference | GitHub

Workflow Clients are embedded in your application code, and connect to Temporal Server via gRPC. They are the only way to schedule new Workflow Executions with Temporal Server.

  • Workflow Clients can run in any Node.js application, for example, in a serverless function, Express.js API route handler or CLI/script run.
  • The primary use of Workflow Clients is to start new Workflow Executions (including Cron Workflows). Given a workflowId, Workflow Clients may also get a Handle to a running Workflow Execution or retrieve/wait for its result.
  • Workflow Handles are bindings to specific Workflow Executions that expose more APIs for control. We strongly recommend familiarising yourself with Workflow Handle APIs as they are the main way you will signal, query, describe, cancel, terminate and await the result of running Workflow Executions.

Workflow Clients are separate from Workers, but communicate with them via Task Queues to start Workflow Executions. See the dedicated Workers and Task Queues docs and Workflow docs for more info.

Full Example​

Here is example WorkflowClient code from our Hello World sample:

import { Connection, WorkflowClient } from '@temporalio/client';
import { example } from './workflows';

async function run() {
const connection = new Connection(); // Connect to localhost with default ConnectionOptions.
// In production, pass options to the Connection constructor to configure TLS and other settings.
// This is optional but we leave this here to remind you there is a gRPC connection being established.

const client = new WorkflowClient(connection.service, {
// In production you will likely specify `namespace` here; it is 'default' if omitted
});

// Invoke the `example` Workflow, only resolved when the workflow completes
const handle = await client.start(example, {
args: ['Temporal'], // type inference works! args: [name: string]
taskQueue: 'tutorial',
workflowId: 'my-business-id',
});
console.log(`Started workflow ${handle.workflowId}`);
// optional: wait for client result
// console.log(await handle.result()); // Hello, Temporal!
}

run().catch((err) => {
console.error(err);
process.exit(1);
});

The rest of this doc explains each step in detail with practical usage tips.

Create a new Workflow Client​

Create a WorkflowClient with the requisite gRPC Connection:

import { Connection, WorkflowClient } from '@temporalio/client';
const connection = new Connection(); // to configure for production
const client = new WorkflowClient(connection.service);

If you omit the connection and just call new WorkflowClient(), it creates a default connection that will work locally. Just remember you will need to configure your Connection and namespace when deploying to production.

Start a Workflow Execution​

When you have a Workflow Client, you can schedule the start of a workflow with client.start, specifying workflowId, taskQueue, and args and returning a Workflow Handle (see below) immediately after the Server acknowledges receipt.

// // STEP ONE: client.start
// Option 1: Specifying args and workflowId
const handle = await client.start(example, {
workflowId: 'business-meaningful-id',
taskQueue: 'tutorial',
args: ['foo', 'bar', 'baz'], // this is typechecked against workflowFn's args
});

// Option 2: Just using string name; no need to import Workflow, but no type inference
import { WorkflowStartOptions } from '@temporalio/client';
type WFType = (key: number) => Promise<string>; // arg types intentionally wrong to prove a point
const handle = await client.start<string>('example', {
workflowId: 'business-meaningful-id',
taskQueue: 'tutorial',
args: [123], // typechecked, but actually wrong at runtime because wrong type signature
} as WorkflowStartOptions<WFType>);

// // STEP TWO: client.getHandle
// Continue in a different process (such as a serverless function)
const handle = client.getHandle(workflowId);
const result = await handle.result(); // wait for Workflow to complete and get result. See below for other Handle APIs

// alternative combination of STEP ONE + TWO
const result = await client.execute(example /*...*/); // start and immediately wait for Workflow to complete and get result
Note: Scheduling is not the same as Starting Workflows

Calling client.start (or client.execute) merely sends a Command to Temporal Server to schedule a new Workflow Execution on the specified Task Queue; it does not actually start until a Worker (that has a matching Workflow Type) polling that Task Queue picks it up.

You can test this by executing a Workflow Client command without a matching Worker. Temporal Server records the command in Event History but does not make progress with the Workflow Execution until a Worker starts polling with a matching Task Queue and Workflow Definition.

This queuing mechanic makes your application tolerant to outages and horizontally scalable, but can be confusing to newcomers if they expect that calling client.execute(MyWorkflow) directly executes the Workflow code on the same machine as the Client.

Workflow Options​

A brief guide to the WorkflowOptions available to you:

  • workflowId, taskQueue, and args (if required) are the main ones you will regularly use
  • Optional features:
  • Advanced features you probably won't need: followRuns and workflowIdReusePolicy.
Workflow-level Retries and Timeouts not recommended

You will see that there are workflowRunTimeout, workflowExecutionTimeout, workflowTaskTimeout, and retryPolicy options in WorkflowOptions. We strongly recommend not using them unless you know what you are doing. Do not rely on Workflows to timeout or fail - you probably want to push this logic down to an Activity instead.

Workflow Handle APIs​

Workflow Handles are returned after you start a Workflow (or retrieve an existing one with client.getHandle) and are bound to a single Workflow instance. They represent already-started Workflow Executions, and let you signal, query, describe, cancel, or terminate their instance:

// Get a handle if you don't already have it
const handle = client.getHandle(workflowId);

// Handle API quick examples
await handle.cancel(); // cancel with cleanup
await handle.terminate(); // kill immediately
const WFdescription = await handle.describe(); // get Workflow Execution internal info
await handle.signal<Args>(mySignal, ...args); // see Signal docs
const queryResult = await handle.query<ReturnType, Args>(myQuery, ...args); // see Query docs
const result = await handle.result(); // block until the workflow completes and/or get return value
const result = await client.execute(example /*...*/); // Alternative API for starting and immediately waiting for Workflow completion

The Workflow Handle APIs let you externally control your Workflow:

Handle APIDescription
clientReadonly accessor to the underlying WorkflowClient.
workflowIdThe workflowId of the current Workflow.
originalRunIdThe runId of the initial run of the bound Workflow.
query()Call to query a Workflow after it's been started even if it has already completed. const value = await handle.query(getValue, ...args);
signal()Call to signal a running Workflow. await handle.signal(increment, ...args);
cancel()Cancel a running Workflow.
terminate()Terminate a running Workflow
describe()Describe the current workflow execution
result()Promise that resolves when Workflow execution completes

The following covers how to use many of these APIs, you will want to be fluent with them as they cover the basics of Workflow manipulation.

Get a Workflow's result​

Workflow functions may or may not return a result when they complete.

If you started a Workflow with handle.start, you can choose to wait for the result anytime with handle.result(). This

const handle = client.getHandle(workflowId);
const result = await handle.result(); // block until the workflow completes, if you wish

Using a Workflow Handle isn't necessary with client.execute by definition.

  • Don't forget to handle errors here - if you call result() on a Workflow that prematurely ended for some reason, it will throw an Error reflecting that reason.
  • You can also specify a runId, but you will almost never need it, because most people only want the results of the latest run (a Workflow may run multiple times if failed or continued as new).

Cancel a Workflow​

To cancel a Workflow execution, call the handle.cancel() method on a WorkflowHandle.

// Start the Workflow without waiting its completion
await handle.start(args);
// ... Later on, cancel the workflow
await handle.cancel();

With handle.cancel(), Timers and Child Workflows have the opportunity to execute cleanup code. If you wish to skip that, you can also handle.terminate() forcefully.

Temporal gives you fine grained control over what happens when you cancel a workflow. See our docs on Cancellation Scopes for details and examples.

Scheduling Cron Workflows​

You can set each workflow to repeat on a schedule with the cronSchedule option:

const handle = await client.start(scheduledWorkflow, {
workflowId: 'business-meaningful-id',
taskQueue: 'tutorial',
cronSchedule: '* * * * *', // start every minute
});
Should I use Cron Workflows or Timers?

This section is specifically about Temporal Cron Jobs, which are Workflows that have the cronSchedule option set in Temporal. Because Temporal Workflows have Timers, can loop indefinitely, and can spawn Child Workflows, it is natural to ask when to use which.

Cron Workflows are rigid and come with a lot of caveats. They are a great choice if you have Workflows that need to run as rigidly as the native Linux cron utility (except distributed and fault tolerant). However, if you have any advanced needs (including needing overlaps, or canceling individual executions without affecting the overall schedule), use Timers.

You can set each Workflow to repeat on a schedule with the cronSchedule option:

const handle = await client.start(scheduledWorkflow, {
taskQueue: 'test',
cronSchedule: '* * * * *', // start every minute
});

Typescript SDK workflowOptions source code: https://typescript.temporal.io/api/interfaces/client.workflowoptions/#cronschedule

Note: Child Workflows and External Workflows​

You can start Child Workflows only from within another Workflow, not from a Client.

Hence the main Child Workflows documentation is on the Workflow APIs page.

A lot of the same concepts about starting, executing, and signaling Workflow Executions apply:

// inside Workflow code
import { startChild } from '@temporalio/workflow';

export async function example(WFname: string, args: string[]): Promise<string> {
const childHandle = await startChild(WFname, {
// workflowId is optional only for child workflows
// task queue and other options inherited from parent, can override
args,
});
const result = await childHandle.result();
// // equivalent to
// const result = await executeChild(WFname, /* ... */)
return result;
}

You should use cancellationScopes if you need to cancel Child Workflows.

The same concept of "Workflow Handles" applies to retrieving handles for Child and External Workflowsβ€”as long as you have the Workflow ID:

// inside Workflow code
import { getExternalWorkflowHandle } from '@temporalio/workflow';

export async function CancelExternalWorkflow(wfId: string): void {
const extHandle = getExternalWorkflowHandle(wfId);
// ...
}

Again, see the Workflow APIs documentation for full details.

Get notified of updates