Manage Interceptors - TypeScript SDK
Interceptors are a mechanism for modifying inbound and outbound SDK calls. Interceptors are commonly used to add tracing and authorization to the scheduling and execution of Workflows and Activities. You can compare these to "middleware" in other frameworks.
How to implement interceptors in TypeScript
The TypeScript SDK comes with an optional interceptor package that adds tracing with OpenTelemetry. See how to use it in the interceptors-opentelemetry code sample.
- WorkflowInboundCallsInterceptor: Runs on the Worker in the Workflow definition and intercepts inbound calls like starts/executes, Signals, and Queries.
- WorkflowOutboundCallsInterceptor: Runs on the Worker in the Workflow definition and intercepts outbound calls to Temporal APIs like starting Activities and Timers.
- ActivityInboundCallsInterceptor: Runs on the Worker in the Activity at the beginning.
- WorkflowClientInterceptor: Runs on the client and intercepts workflow-related methods of
ClientandWorkflowHandlelike starting or signaling a Workflow.
Workflow inbound and outbound interceptor methods execute during replay. Use replay-safe APIs for logging, randomness, and time in these interceptors. See Develop Workflow logic for details.
Activity and Client interceptors do not run in the Workflow and therefore are not affected by replay.
Interceptors are run in a chain, and all interceptors work similarly.
They accept two arguments: input and next, where next calls the next interceptor in the chain.
All interceptor methods are optional—it's up to the implementor to choose which methods to intercept.
Interceptor examples
Log start and completion of Activities
import {
ActivityInput,
Next,
WorkflowOutboundCallsInterceptor,
} 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,
});
}
}
}
Authorization
import {
defaultDataConverter,
Next,
WorkflowInboundCallsInterceptor,
WorkflowInput,
} from '@temporalio/workflow';
/**
* WARNING: This demo is meant as a simple auth example.
* Do not use this for actual authorization logic.
* Auth headers should be encrypted and credentials
* stored outside of the codebase.
*/
export class DumbWorkflowAuthInterceptor
implements WorkflowInboundCallsInterceptor
{
public async execute(
input: WorkflowInput,
next: Next<WorkflowInboundCallsInterceptor, 'execute'>,
): Promise<unknown> {
const authHeader = input.headers.auth;
const { user, password } = authHeader
? await defaultDataConverter.fromPayload(authHeader)
: undefined;
if (!(user === 'admin' && password === 'admin')) {
throw new Error('Unauthorized');
}
return await next(input);
}
}
To properly do authorization from Workflow code, the Workflow would need to access encryption keys and possibly authenticate against an external user database, which requires the Workflow to break isolation. Please contact us if you need to discuss this further.
Register an Interceptor
Register via a Plugin
If you're building a reusable library or want to bundle interceptors with other primitives, you can register them through a Plugin.
Activity and client interceptors registration
-
Activity interceptors are registered on Worker creation by passing an array of ActivityInboundCallsInterceptor factory functions through WorkerOptions.
-
Client interceptors are registered on
Clientconstruction by passing an array of WorkflowClientInterceptor via ClientOptions.interceptors.
Workflow interceptors registration
Workflow interceptor registration is different from the other interceptors because they run in the Workflow isolate.
To register Workflow interceptors, export an interceptors function from a file located in the workflows directory and provide the name of that file to the Worker on creation via WorkerOptions.
At the time of construction, the Workflow context is already initialized for the current Workflow.
You may call the workflowInfo() function to access Workflow-specific information from an interceptor.
src/workflows/your-interceptors.ts
import { workflowInfo } from '@temporalio/workflow';
export const interceptors = () => ({
outbound: [new ActivityLogInterceptor(workflowInfo().workflowType)],
inbound: [],
});
src/worker/index.ts
const worker = await Worker.create({
workflowsPath: require.resolve('./workflows'),
interceptors: {
workflowModules: [require.resolve('./workflows/your-interceptors')],
},
});