Use when implementing Temporal Nexus in TypeScript to connect Temporal applications across Namespaces. Covers defining Nexus Service contracts, building synchronous and asynchronous Operation handlers, creating caller Workflows, registering handlers in Workers, configuring Nexus Endpoints, error handling with Nexus exception classes, cancellation patterns, and deploying cross-Namespace Nexus calls on Temporal Cloud with mTLS.
Instructions for implementing Temporal Nexus in TypeScript to connect Temporal applications across Namespaces using typed service contracts and operations.
Do not use Nexus when a simple Child Workflow or Activity within the same Namespace is sufficient.
Before implementing Nexus, determine the deployment context by asking the user:
temporal server start-dev on localhost:7233)?temporal.internal:7233)?<namespace>.<accountId>.tmprl.cloud:7233)?localhost:8233)These answers determine connection config, mTLS requirements, and CLI commands used below.
Temporal Nexus connects Temporal Applications within and across Namespaces using three primitives:
| Concept | Purpose |
|---|---|
| Nexus Endpoint | Routes requests from a caller Namespace to a handler Namespace + task queue |
| Nexus Service | A contract defining named Operations with typed inputs/outputs |
| Nexus Operation | A single callable unit -- either synchronous (inline) or asynchronous (backed by a Workflow) |
Status: Pre-release. All APIs are experimental and may have backwards-incompatible changes.
Key packages:
nexus-rpc -- service/operation definitions, serviceHandler, error classes@temporalio/nexus -- WorkflowRunOperationHandler, startWorkflow, getClient@temporalio/workflow -- createNexusClient (caller side)@temporalio/worker -- nexusServices option on Worker.create| Task | API / Command |
|---|---|
| Start dev server | temporal server start-dev |
| Create namespace | temporal operator namespace create --namespace <name> |
| Create endpoint (local) | temporal operator nexus endpoint create --name <ep> --target-namespace <ns> --target-task-queue <tq> |
| Create endpoint (Cloud) | tcld nexus endpoint create --name <ep> --target-namespace <ns.acct> --target-task-queue <tq> --allow-namespace <caller.acct> |
| Define service contract | nexus.service('name', { op: nexus.operation<In, Out>() }) |
| Sync operation handler | async (ctx, input) => { return result; } |
| Async operation handler | new temporalNexus.WorkflowRunOperationHandler(async (ctx, input) => { ... }) |
| Start workflow in handler | temporalNexus.startWorkflow(ctx, workflowFn, { args: [input], workflowId }) |
| Get Temporal client in handler | temporalNexus.getClient() |
| Register handler in Worker | Worker.create({ nexusServices: [handler] }) |
| Create caller Nexus client | wf.createNexusClient({ service, endpoint }) |
| Execute operation from caller | nexusClient.executeOperation('opName', input, { scheduleToCloseTimeout }) |
| Describe workflow (CLI) | temporal workflow describe -w <ID> |
| Show workflow history (CLI) | temporal workflow show -w <ID> |
// src/api.ts
import * as nexus from 'nexus-rpc';
export const myService = nexus.service('my-service', {
echo: nexus.operation<EchoInput, EchoOutput>(),
processOrder: nexus.operation<OrderInput, OrderOutput>(),
});
export interface EchoInput { message: string; }
export interface EchoOutput { message: string; }
export interface OrderInput { orderId: string; items: string[]; }
export interface OrderOutput { status: string; }
Inputs and outputs are plain TypeScript objects serialized as JSON by default. For Protobuf, use ProtobufJsonPayloadConverter.
Use for short-lived computations, queries, signals, or calls to external services.
// src/handler.ts
import * as nexus from 'nexus-rpc';
import * as temporalNexus from '@temporalio/nexus';
import { myService, EchoInput, EchoOutput } from './api';
export const myServiceHandler = nexus.serviceHandler(myService, {
echo: async (ctx, input: EchoInput): Promise<EchoOutput> => {
// Can also use temporalNexus.getClient() to query/signal workflows
return { message: input.message };
},
// ... other operations
});
Use when the operation is long-running and should be backed by a durable Workflow.
// src/handler.ts
import { randomUUID } from 'crypto';
import * as nexus from 'nexus-rpc';
import * as temporalNexus from '@temporalio/nexus';
import { myService, OrderInput, OrderOutput } from './api';
import { processOrderWorkflow } from './workflows';
export const myServiceHandler = nexus.serviceHandler(myService, {
// ...sync handlers...
processOrder: new temporalNexus.WorkflowRunOperationHandler<OrderInput, OrderOutput>(
async (ctx, input: OrderInput) => {
return await temporalNexus.startWorkflow(ctx, processOrderWorkflow, {
args: [input],
// Use ctx.requestId for deduplication (stable across retries)
workflowId: input.orderId ?? ctx.requestId ?? randomUUID(),
// taskQueue defaults to the handler's task queue
});
},
),
});
Key points:
ctx.requestId is stable across retries -- use it for deduplication when no business ID exists.args array for the workflow.workflowId should be a business-meaningful ID when possible.// src/handler-worker.ts
import { Worker, NativeConnection } from '@temporalio/worker';
import { myServiceHandler } from './handler';
async function run() {
const connection = await NativeConnection.connect({ address: 'localhost:7233' });
const worker = await Worker.create({
connection,
namespace: 'my-target-namespace',
taskQueue: 'my-handler-task-queue',
workflowsPath: require.resolve('./workflows'),
nexusServices: [myServiceHandler], // <-- register here
});
await worker.run();
}
run().catch(console.error);
// src/caller-workflows.ts
import * as wf from '@temporalio/workflow';
import { myService } from './api';
const ENDPOINT_NAME = 'my-nexus-endpoint-name';
export async function callerWorkflow(orderId: string, items: string[]): Promise<string> {
const nexusClient = wf.createNexusClient({
service: myService,
endpoint: ENDPOINT_NAME,
});
const result = await nexusClient.executeOperation(
'processOrder',
{ orderId, items },
{ scheduleToCloseTimeout: '30s' }
);
return result.status;
}
// src/caller-worker.ts
import { Worker, NativeConnection } from '@temporalio/worker';
async function run() {
const connection = await NativeConnection.connect({ address: 'localhost:7233' });
const worker = await Worker.create({
connection,
namespace: 'my-caller-namespace',
taskQueue: 'my-caller-task-queue',
workflowsPath: require.resolve('./caller-workflows'),
});
await worker.run();
}
run().catch(console.error);
// src/starter.ts
import { Client, Connection } from '@temporalio/client';
async function run() {
const connection = await Connection.connect({ address: 'localhost:7233' });
const client = new Client({ connection, namespace: 'my-caller-namespace' });
const result = await client.workflow.execute('callerWorkflow', {
taskQueue: 'my-caller-task-queue',
workflowId: 'caller-1',
args: ['order-123', ['item-a', 'item-b']],
});
console.log('Result:', result);
}
run().catch(console.error);
# Start dev server
temporal server start-dev
# Create namespaces
temporal operator namespace create --namespace my-target-namespace
temporal operator namespace create --namespace my-caller-namespace
# Create Nexus endpoint
temporal operator nexus endpoint create \
--name my-nexus-endpoint-name \
--target-namespace my-target-namespace \
--target-task-queue my-handler-task-queue
# Install tcld
brew install temporalio/brew/tcld
# Generate certs (if needed)
tcld gen ca --org $YOUR_ORG --validity-period 1y --ca-cert ca.pem --ca-key ca.key
# Login
tcld login
# Create namespaces (if needed)
tcld namespace create \
--namespace <caller-ns> \
--cloud-provider aws --region us-west-2 \
--ca-certificate-file ca.pem --retention-days 1
tcld namespace create \
--namespace <target-ns> \
--cloud-provider aws --region us-west-2 \
--ca-certificate-file ca.pem --retention-days 1
# Create endpoint with allowlist
tcld nexus endpoint create \
--name my-nexus-endpoint-name \
--target-namespace <target-ns.account> \
--target-task-queue my-handler-task-queue \
--allow-namespace <caller-ns.account>
On Cloud, use --allow-namespace to control which caller Namespaces can use the endpoint.
| Exception Class | Package | When to Use |
|---|---|---|
OperationError | nexus-rpc | Operation failed due to application logic; should NOT be retried |
HandlerError | nexus-rpc | Throw with a HandlerErrorType to control retryability |
NexusOperationFailure | @temporalio/nexus | Caught in caller Workflow when an operation fails; inspect .cause |
HandlerErrorType retryability:
BAD_REQUEST, UNAUTHENTICATED, UNAUTHORIZED, NOT_FOUND, NOT_IMPLEMENTEDRESOURCE_EXHAUSTED, INTERNAL, UNAVAILABLE, UPSTREAM_TIMEOUTimport { OperationError, HandlerError } from 'nexus-rpc';
// In a handler:
throw new OperationError('Order not found'); // non-retryable app error
throw new HandlerError('Service overloaded', 'RESOURCE_EXHAUSTED'); // retryable
// In a caller workflow:
import { NexusOperationFailure } from '@temporalio/common';
try {
await nexusClient.executeOperation('processOrder', input, opts);
} catch (err) {
if (err instanceof NexusOperationFailure) {
console.error('Nexus op failed:', err.cause);
}
}
For granular cancellation, wrap the operation in an explicit CancellationScope:
import { CancellationScope } from '@temporalio/workflow';
const scope = new CancellationScope();
const handle = scope.run(() =>
nexusClient.executeOperation('longRunning', input, opts)
);
// Later:
scope.cancel();
See the nexus-cancellation sample for a complete example.
Workflow history events (caller side):
| Operation Type | Events |
|---|---|
| Synchronous | NexusOperationScheduled, NexusOperationCompleted |
| Asynchronous | NexusOperationScheduled, NexusOperationStarted, NexusOperationCompleted |
CLI commands:
temporal workflow describe -w <ID> -- shows pending Nexus operations and callbackstemporal workflow show -w <ID> -- shows full event history including Nexus eventscreateNexusClient({ endpoint }) in the caller Workflow.--target-task-queue) and Worker.create({ taskQueue }) in the handler.nexusServices in the handler Worker -- the Worker will not pick up Nexus requests without it.scheduleToCloseTimeout on executeOperation -- this is required.--allow-namespace on Temporal Cloud -- the caller namespace must be in the endpoint's allowlist.pip install instead of uv pip install if your project also has Python components.http://localhost:8233 (or your ingress URL) for Nexus endpoint configuration and Workflow history events.temporal workflow show -w <ID> on both caller and handler Workflows to trace the full Nexus call chain.temporal operator nexus endpoint list (local) or check Cloud UI at https://cloud.temporal.io/nexus.NexusOperationFailure.cause in the caller Workflow for the root cause of handler failures.If this skill does not answer your question, use Context7 to search /temporalio/sdk-typescript or /temporalio/samples-typescript for more details.