Use when implementing, registering, or debugging Temporal TypeScript SDK interceptors for workflows, activities, or clients. Covers inbound and outbound interceptor patterns, OpenTelemetry tracing setup, authorization guards, activity logging, registration on Workers and Clients, and the workflow-isolate constraint for workflow interceptors.
Interceptors are Temporal's equivalent of middleware. They modify inbound and outbound SDK calls by forming a chain. Each interceptor receives input and a next function that delegates to the next interceptor in the chain.
Common use cases:
Before implementing interceptors, confirm the following with the user:
temporal server start-dev, Docker Compose, Kubernetes, Temporal Cloud)my-namespace.abc123.tmprl.cloud:7233)temporal CLI must reach the cluster from outside)These answers determine connection options and whether mTLS certificates are required when constructing the Client.
| Interceptor Interface | Where It Runs | What It Intercepts | Registration Location |
|---|---|---|---|
WorkflowInboundCallsInterceptor | Workflow isolate | Execution, Signals, Queries, Updates | workflowModules on Worker |
WorkflowOutboundCallsInterceptor | Workflow isolate | Scheduling Activities, starting Timers, Continue-As-New | workflowModules on Worker |
ActivityInboundCallsInterceptor | Worker (Node.js) | Activity execute calls | interceptors.activity on Worker |
WorkflowClientInterceptor | Client (Node.js) | Client.start, Client.signal, WorkflowHandle methods | interceptors on Client constructor |
Intercept outbound calls from a workflow to log every activity that is scheduled.
// src/workflows/interceptors.ts
import {
ActivityInput,
Next,
WorkflowOutboundCallsInterceptor,
workflowInfo,
} from '@temporalio/workflow';
export class ActivityLogInterceptor
implements WorkflowOutboundCallsInterceptor
{
constructor(public readonly workflowType: string) {}
async scheduleActivity(
input: ActivityInput,
next: Next<WorkflowOutboundCallsInterceptor, 'scheduleActivity'>,
): Promise<unknown> {
console.log('Starting activity', { activityType: input.activityType });
try {
return await next(input);
} finally {
console.log('Completed activity', {
workflow: this.workflowType,
activityType: input.activityType,
});
}
}
}
// Export the interceptors factory function (REQUIRED for workflow interceptors)
export const interceptors = () => ({
outbound: [new ActivityLogInterceptor(workflowInfo().workflowType)],
inbound: [],
});
Inspect headers on inbound workflow execution for auth tokens.
// src/workflows/auth-interceptor.ts
import {
defaultDataConverter,
Next,
WorkflowInboundCallsInterceptor,
WorkflowInput,
} from '@temporalio/workflow';
export class WorkflowAuthInterceptor
implements WorkflowInboundCallsInterceptor
{
async execute(
input: WorkflowInput,
next: Next<WorkflowInboundCallsInterceptor, 'execute'>,
): Promise<unknown> {
const authHeader = input.headers.auth;
if (!authHeader) {
throw new Error('Missing auth header');
}
const { user, role } = await defaultDataConverter.fromPayload(authHeader);
if (role !== 'admin') {
throw new Error('Unauthorized: admin role required');
}
return await next(input);
}
}
export const interceptors = () => ({
inbound: [new WorkflowAuthInterceptor()],
outbound: [],
});
Intercept activity execution on the Worker side (runs in normal Node.js, not the workflow isolate).
// src/activities/interceptors.ts
import {
ActivityInboundCallsInterceptor,
ActivityExecuteInput,
Next,
} from '@temporalio/worker';
import { Context } from '@temporalio/activity';
export class ActivityMetricsInterceptor
implements ActivityInboundCallsInterceptor
{
async execute(
input: ActivityExecuteInput,
next: Next<ActivityInboundCallsInterceptor, 'execute'>,
): Promise<unknown> {
const startTime = Date.now();
try {
return await next(input);
} finally {
const duration = Date.now() - startTime;
console.log(`Activity ${Context.current().info.activityType} took ${duration}ms`);
}
}
}
Intercept client-side calls such as starting or signaling a workflow.
// src/client/interceptors.ts
import {
WorkflowClientInterceptor,
WorkflowStartInput,
Next,
} from '@temporalio/client';
export class ClientLoggingInterceptor implements WorkflowClientInterceptor {
async start(
input: WorkflowStartInput,
next: Next<WorkflowClientInterceptor, 'start'>,
): Promise<unknown> {
console.log('Starting workflow', {
workflowId: input.workflowId,
workflowType: input.workflowType,
});
return await next(input);
}
}
Workflow interceptors run inside the deterministic V8 isolate. You must:
interceptors factory function from a file in your workflows directory.workflowModules.// src/worker.ts
import { Worker } from '@temporalio/worker';
const worker = await Worker.create({
workflowsPath: require.resolve('./workflows'),
interceptors: {
workflowModules: [require.resolve('./workflows/interceptors')],
},
taskQueue: 'my-task-queue',
});
Pass factory functions directly on Worker creation:
import { Worker } from '@temporalio/worker';
import { ActivityMetricsInterceptor } from './activities/interceptors';
const worker = await Worker.create({
workflowsPath: require.resolve('./workflows'),
activities,
interceptors: {
activity: [() => new ActivityMetricsInterceptor()],
},
taskQueue: 'my-task-queue',
});
Pass interceptors when constructing the Client:
import { Client } from '@temporalio/client';
import { ClientLoggingInterceptor } from './client/interceptors';
const client = new Client({
interceptors: {
workflow: [new ClientLoggingInterceptor()],
},
});
The SDK ships @temporalio/interceptors-opentelemetry for automatic span propagation.
npm install @temporalio/interceptors-opentelemetry @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-grpc
See the full working sample: temporalio/samples-typescript/interceptors-opentelemetry.
| Mistake | Why It Fails | Fix |
|---|---|---|
Registering workflow interceptors via interceptors.activity on the Worker | Workflow interceptors run in the V8 isolate, not the Node.js runtime | Use interceptors.workflowModules and export an interceptors factory function |
| Importing Node.js modules (fs, http) inside a workflow interceptor file | The workflow isolate forbids non-deterministic imports | Keep workflow interceptors pure; move I/O to activity interceptors |
Forgetting to call next(input) | Breaks the interceptor chain; downstream interceptors and the actual call never execute | Always return await next(input) unless you intentionally want to short-circuit |
Mutating input directly | May cause unexpected side effects across the chain | Clone or spread input if you need to modify it before passing to next |
Exporting a class instead of an interceptors factory function for workflow interceptors | The SDK expects a function export named interceptors | Export const interceptors = () => ({ inbound: [...], outbound: [...] }) |
workflowModules (for workflow interceptors) or the factory is in the correct interceptors array on Worker/Client creation.@temporalio/workflow imports are safe there.workflowInfo() inside workflow interceptor constructors to access the current workflow type, run ID, and other metadata for conditional logic.input and a mock next function before integrating into a Worker.If this skill does not answer your question, use Context7 to search /temporalio/sdk-typescript or /temporalio/samples-typescript for more details. Useful queries: