Skip to main content

Temporal Client - Typescript SDK

A Temporal Client enables you to communicate with the Temporal Service. Communication with a Temporal Service lets you perform actions such as starting Workflow Executions, sending Signals and Queries to Workflow Executions, getting Workflow results, and more. You cannot initialize a Temporal Client inside a Workflow. However, they're commonly initialized inside an Activity to communicate with a Temporal Service.

This page shows you how to do the following using the TypeScript SDK with the Temporal Client:

In the TypeScript SDK, connecting to Temporal Service from a Temporal Application and from within an Activity rely on a different type of connection than connecting from a Worker. The sections Connect to a local development Temporal Service and Connect to Temporal Cloud apply to connecting from a Temporal Application or from within an Activity. See Connect to Temporal Service from a Worker for details on connecting from a Worker.

Connect to development Temporal Service

To connect to a development Temporal service from a Temporal Application or from within an Activity, import the Connection class from @temporalio/client and use Connection.connect to create a Connection object to connect to the Temporal Service. Then pass in that connection when you create a new Client instance. If you leave the connection options empty, the SDK defaults to connecting to 127.0.0.1:7233 in the default Namespace.

import { Connection, Client } from '@temporalio/client';

async function run() {
const connection = await Connection.connect();
const client = new Client({ connection });
}

If you need to connect to a Temporal Service with custom options, you can provide connection options directly in code, load them from environment variables, or a TOML configuration file using the @temporalio/envconfig helpers. We recommend environment variables or a configuration file for secure, repeatable configuration.

You can use a TOML configuration file to set connection options for the Temporal Client. The configuration file lets you configure multiple profiles, each with its own set of connection options. You can then specify which profile to use when creating the Temporal Client.

You can use the environment variable TEMPORAL_CONFIG_FILE to specify the location of the TOML file or provide the path to the file directly in code. If you don't provide the configuration file path, the SDK looks for it at the path ~/.config/temporalio/temporal.toml or the equivalent on your OS. Refer to Environment Configuration for more details about configuration files and profiles.

info

The connection options set in configuration files have lower precedence than environment variables. This means that if you set the same option in both the configuration file and as an environment variable, the environment variable value overrides the option set in the configuration file.

For example, the following TOML configuration file defines two profiles: default and prod. Each profile has its own set of connection options.

config.toml
# Default profile for local development
[profile.default]
address = "localhost:7233"
namespace = "default"

# Optional: Add custom gRPC headers
[profile.default.grpc_meta]
my-custom-header = "development-value"
trace-id = "dev-trace-123"

# Production profile for Temporal Cloud
[profile.prod]
address = "your-namespace.a1b2c.tmprl.cloud:7233"
namespace = "your-namespace"
api_key = "your-api-key-here"

# TLS configuration for production
[profile.prod.tls]
# TLS auto-enables when TLS config or an API key is present
# disabled = false
client_cert_path = "/etc/temporal/certs/client.pem"
client_key_path = "/etc/temporal/certs/client.key"

# Custom headers for production
[profile.prod.grpc_meta]
environment = "production"
service-version = "v1.2.3"

You can create a Temporal Client using a profile from the configuration file as follows. In this example, you load the default profile for local development:

env-config/src/load-from-file.ts

import { Connection, Client } from '@temporalio/client';
import { loadClientConnectConfig } from '@temporalio/envconfig';
import { resolve } from 'path';

async function main() {
console.log('--- Loading default profile from config.toml ---');

// For this sample to be self-contained, we explicitly provide the path to
// the config.toml file included in this directory.
// By default though, the config.toml file will be loaded from
// ~/.config/temporalio/temporal.toml (or the equivalent standard config directory on your OS).
const configFile = resolve(__dirname, '../config.toml');

// loadClientConnectConfig is a helper that loads a profile and prepares
// the configuration for Connection.connect and Client. By default, it loads the
// "default" profile.
const config = loadClientConnectConfig({
configSource: { path: configFile },
});

console.log(`Loaded 'default' profile from ${configFile}.`);
console.log(` Address: ${config.connectionOptions.address}`);
console.log(` Namespace: ${config.namespace}`);
console.log(` gRPC Metadata: ${JSON.stringify(config.connectionOptions.metadata)}`);

console.log('\nAttempting to connect to client...');
try {
const connection = await Connection.connect(config.connectionOptions);
const client = new Client({ connection, namespace: config.namespace });
console.log('✅ Client connected successfully!');
await connection.close();
} catch (err) {
console.log(`❌ Failed to connect: ${err}`);
}
}

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

Connect to Temporal Cloud

You can connect to Temporal Cloud using either an API key or through mTLS. Connection to Temporal Cloud or any secured Temporal Service requires additional connection options compared to connecting to an unsecured local development instance:

  • Your credentials for authentication.
    • If you are using an API key, provide the API key value.
    • If you are using mTLS, provide the mTLS CA certificate and mTLS private key.
  • Your Namespace and Account ID combination, which follows the format <namespace_id>.<account_id>.
  • The endpoint may vary. The most common endpoint used is the gRPC regional endpoint, which follows the format: <region>.<cloud_provider>.api.temporal.io:7233.
  • For Namespaces with High Availability features with API key authentication enabled, use the gRPC Namespace endpoint: <namespace>.<account>.tmprl.cloud:7233. This allows automated failover without needing to switch endpoints.

You can find the Namespace and Account ID, as well as the endpoint, on the Namespaces tab:

The Namespace and Account ID combination on the left, and the regional endpoint on the right

You can provide these connection options using environment variables, a configuration file, or directly in code.

You can use a TOML configuration file to set connection options for the Temporal Client. The configuration file lets you configure multiple profiles, each with its own set of connection options. You can then specify which profile to use when creating the Temporal Client. For a list of all available configuration options you can set in the TOML file, refer to Environment Configuration.

You can use the environment variable TEMPORAL_CONFIG_FILE to specify the location of the TOML file or provide the path to the file directly in code. If you don't provide the path to the configuration file, the SDK looks for it at the default path ~/.config/temporalio/temporal.toml.

info

The connection options set in configuration files have lower precedence than environment variables. This means that if you set the same option in both the configuration file and as an environment variable, the environment variable value overrides the option set in the configuration file.

For example, the following TOML configuration file defines a staging profile with the necessary connection options to connect to Temporal Cloud via an API key:

# Cloud profile for Temporal Cloud
[profile.staging]
address = "your-namespace.a1b2c.tmprl.cloud:7233"
namespace = "your-namespace"
api_key = "your-api-key-here"

If you want to use mTLS authentication instead of an API key, replace the api_key field with your mTLS certificate and private key:

# Cloud profile for Temporal Cloud
[profile.staging]
address = "your-namespace.a1b2c.tmprl.cloud:7233"
namespace = "your-namespace"
tls_client_cert_data = "your-tls-client-cert-data"
tls_client_key_path = "your-tls-client-key-path"

With the connections options defined in the configuration file, use the loadClientConnectConfig helper from @temporalio/envconfig to load the staging profile from the configuration file. You can then pass the resulting configuration to the Connection.connect method. After that, you then pass the connection object and the Namespace to the Client constructor to create a Temporal Client using the staging profile as follows. After loading the profile, you can also programmatically override specific connection options before creating the client.

env-config/src/load-profile.ts

import { Connection, Client } from '@temporalio/client';
import { loadClientConnectConfig } from '@temporalio/envconfig';
import { resolve } from 'path';

async function main() {
console.log("--- Loading 'staging' profile with programmatic overrides ---");

const configFile = resolve(__dirname, '../config.toml');
const profileName = 'staging';

// The 'staging' profile in config.toml has an incorrect address (localhost:9999)
// We'll programmatically override it to the correct address

// Load the 'staging' profile.
const config = loadClientConnectConfig({
profile: profileName,
configSource: { path: configFile },
});

// Override the target host to the correct address.
// This is the recommended way to override configuration values.
config.connectionOptions.address = 'localhost:7233';

console.log(`\nLoaded '${profileName}' profile from ${configFile} with overrides.`);
console.log(` Address: ${config.connectionOptions.address} (overridden from localhost:9999)`);
console.log(` Namespace: ${config.namespace}`);

console.log('\nAttempting to connect to client...');
try {
const connection = await Connection.connect(config.connectionOptions);
const client = new Client({ connection, namespace: config.namespace });
console.log('✅ Client connected successfully!');
await connection.close();
} catch (err) {
console.log(`❌ Failed to connect: ${err}`);
}
}

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

Connect to Temporal Service from a Worker

Connecting to Temporal Service from a Worker requires the same set of connections options as connecting from a Temporal Application or from within an Activity, but the connection type is different. When connecting from a Worker, you create a NativeConnection object instead of a Connection object. The NativeConnection class is imported from @temporalio/worker instead of @temporalio/client. After you create the NativeConnection object, you pass it to Worker.create() when creating the Worker.

To provide connection options to the NativeConnection, you can use environment variables, a configuration file, or directly in code. The following code snippets show how to create a NativeConnection object using each method. Refer to Connect to a local development Temporal Service and Connect to Temporal Cloud for details on how to provide connection options using each method.

Ensure you have a TOML configuration file with the necessary connection options defined. For example, the following TOML configuration file defines a staging profile with the necessary connection options to connect to Temporal Cloud via an API key:

# Cloud profile for Temporal Cloud
[profile.staging]
address = "your-namespace.a1b2c.tmprl.cloud:7233"
namespace = "your-namespace"
api_key = "your-api-key-here"

Use the loadClientConnectConfig helper from @temporalio/envconfig to load the staging profile from the configuration file and create a NativeConnection object as follows:

import { NativeConnection } from '@temporalio/worker';
import { loadClientConnectConfig } from '@temporalio/envconfig';
import { resolve } from 'path';

async function main() {
const configFile = resolve(__dirname, '../config.toml');
const profileName = 'staging'

// Load the 'staging' profile.
const config = loadClientConnectConfig({
profile: profileName,
configSource: { path: configFile },
});

const connection = await NativeConnection.connect(config.connectionOptions);

const worker = await Worker.create({
connection,
namespace: <namespace_id>.<account_id>,
// ...
});
}

NativeConnection, Connection, and Client

NativeConnection, Connection, and Client are all classes provided by the TypeScript SDK to facilitate communication with the Temporal Service. This section explains the differences between these classes and their respective use cases. For detailed information about each class, refer to the Temporal TypeScript API documentation.

NativeConnection vs. Connection

The TypeScript SDK provides two types of connection classes to connect to the Temporal Service: NativeConnection and Connection. The NativeConnection class is used to connect from a Worker, while the Connection class is used to connect from a Temporal Application or from within an Activity, typically through a Client object. Both connection classes accept the same set of connection options.

Connection vs. Client

A Client object is a high-level, lightweight abstraction that simplifies interaction with the Temporal Service. It internally manages a Connection object to handle the low-level communication details. The Client class provides convenient methods for common operations such as starting Workflow Executions, sending Signals and Queries, and retrieving Workflow results.

A Connection object is a lower-level and expensive object that represents a direct connection to the Temporal Service. You pass in a Connection object to the Client constructor to create a Client instance. Since a Connection is expensive to create, create a single Connection object and reuse it across your application whenever possible.

When instantiating a Connection, you specify most connection options except for the Namespace, such as the Temporal Service endpoint, TLS settings, and authentication credentials. When instantiating a Client, you provide the Connection object and the Namespace you want to connect to, along with other client options.

Start Workflow Execution

How to start a Workflow Execution using the Typescript SDK

Workflow Execution semantics rely on several parameters—that is, to start a Workflow Execution you must supply a Task Queue that will be used for the Tasks (one that a Worker is polling), the Workflow Type, language-specific contextual data, and Workflow Function parameters.

In the examples below, all Workflow Executions are started using a Temporal Client. To spawn Workflow Executions from within another Workflow Execution, use either the Child Workflow or External Workflow APIs.

See the Customize Workflow Type section to see how to customize the name of the Workflow Type.

A request to spawn a Workflow Execution causes the Temporal Service to create the first Event (WorkflowExecutionStarted) in the Workflow Execution Event History. The Temporal Service then creates the first Workflow Task, resulting in the first WorkflowTaskScheduled Event.

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

const handle = await client.workflow.start(example, {
workflowId: 'your-workflow-id',
taskQueue: 'your-task-queue',
args: ['argument01', 'argument02', 'argument03'], // this is typechecked against workflowFn's args
});
const handle = client.getHandle(workflowId);
const result = await handle.result();

Calling client.workflow.start() and client.workflow.execute() send 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 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.

Workflow Execution run in a separate V8 isolate context in order to provide a deterministic runtime.

Set a Workflow's Task Queue

In most SDKs, the only Workflow Option that must be set is the name of the Task Queue.

For any code to execute, a Worker Process must be running that contains a Worker Entity that is polling the same Task Queue name.

A Task Queue is a dynamic queue in Temporal polled by one or more Workers.

Workers bundle Workflow code and node modules using Webpack v5 and execute them inside V8 isolates. Activities are directly required and run by Workers in the Node.js environment.

Workers are flexible. You can host any or all of your Workflows and Activities on a Worker, and you can host multiple Workers on a single machine.

The Worker need three main things:

  • taskQueue: The Task Queue to poll. This is the only required argument.
  • activities: Optional. Imported and supplied directly to the Worker.
  • Workflow bundle. Choose one of the following options:
    • Specify workflowsPath pointing to your workflows.ts file to pass to Webpack; for example, require.resolve('./workflows'). Workflows are bundled with their dependencies.
    • If you prefer to handle the bundling yourself, pass a prebuilt bundle to workflowBundle.
import { Worker } from '@temporalio/worker';
import * as activities from './activities';

async function run() {
// Step 1: Register Workflows and Activities with the Worker and connect to
// the Temporal server.
const worker = await Worker.create({
workflowsPath: require.resolve('./workflows'),
activities,
taskQueue: 'hello-world',
});
// Worker connects to localhost by default and uses console.error for logging.
// Customize the Worker by passing more options to create():
// https://typescript.temporal.io/api/classes/worker.Worker
// If you need to configure server connection parameters, see docs:
// /typescript/security#encryption-in-transit-with-mtls

// Step 2: Start accepting tasks on the `tutorial` queue
await worker.run();
}

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

taskQueue is the only required option; however, use workflowsPath and activities to register Workflows and Activities with the Worker.

When scheduling a Workflow, you must specify taskQueue.

import { Client, Connection } from '@temporalio/client';
// This is the code that is used to start a Workflow.
const connection = await Connection.create();
const client = new Client({ connection });
const result = await client.workflow.execute(yourWorkflow, {
// required
taskQueue: 'your-task-queue',
// required
workflowId: 'your-workflow-id',
});

When creating a Worker, you must pass the taskQueue option to the Worker.create() function.

const worker = await Worker.create({
// imported elsewhere
activities,
taskQueue: 'your-task-queue',
});

Optionally, in Workflow code, when calling an Activity, you can specify the Task Queue by passing the taskQueue option to proxyActivities(), startChild(), or executeChild(). If you do not specify taskQueue, the TypeScript SDK places Activity and Child Workflow Tasks in the same Task Queue as the Workflow Task Queue.

Set a Workflow Id

Although it is not required, we recommend providing your own Workflow Idthat maps to a business process or business entity identifier, such as an order identifier or customer identifier.

Connect to a Client with client.workflow.start() and any arguments. Then specify your taskQueue and set your workflowId to a meaningful business identifier.

const handle = await client.workflow.start(example, {
workflowId: 'yourWorkflowId',
taskQueue: 'yourTaskQueue',
args: ['your', 'arg', 'uments'],
});

This starts a new Client with the given Workflow Id, Task Queue name, and an argument.

Get the results of a Workflow Execution

If the call to start a Workflow Execution is successful, you will gain access to the Workflow Execution's Run Id.

The Workflow Id, Run Id, and Namespace may be used to uniquely identify a Workflow Execution in the system and get its result.

It's possible to both block progress on the result (synchronous execution) or get the result at some other point in time (asynchronous execution).

In the Temporal Platform, it's also acceptable to use Queries as the preferred method for accessing the state and results of Workflow Executions.

To return the results of a Workflow Execution:

return 'Completed ' + wf.workflowInfo().workflowId + ', Total Charged: ' + totalCharged;

totalCharged is just a function declared in your code. For a full example, see subscription-workflow-project-template-typescript/src/workflows.ts.

A Workflow function may return a result. If it doesn’t (in which case the return type is Promise<void>), the result will be undefined.

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

const handle = client.getHandle(workflowId);
const result = await handle.result();

Using a Workflow Handle isn't necessary with client.workflow.execute().

Workflows that prematurely end will throw a WorkflowFailedError if you call result().

If you call result() on a Workflow that prematurely ended for some reason, it throws a WorkflowFailedError error that reflects the reason. For that reason, it is recommended to catch that error.

const handle = client.getHandle(workflowId);
try {
const result = await handle.result();
} catch (err) {
if (err instanceof WorkflowFailedError) {
throw new Error('Temporal workflow failed: ' + workflowId, {
cause: err,
});
} else {
throw new Error('error from Temporal workflow ' + workflowId, {
cause: err,
});
}
}