# How to Implement Middleware in AWS SDK for JavaScript v3: A Complete Guide

> Implement AWS SDK for JavaScript v3 middleware to add custom logic. Inject code at five pipeline steps to modify requests before they reach the network. Learn how.

- Repository: [Amazon Web Services/agent-toolkit-for-aws](https://github.com/aws/agent-toolkit-for-aws)
- Tags: how-to-guide
- Published: 2026-07-03

---

**You implement middleware in AWS SDK for JavaScript v3 by calling `client.middlewareStack.add()` to inject custom logic at one of five pipeline steps—initialize, serialize, build, finalizeRequest, or deserialize—allowing you to intercept and modify requests before they reach the network layer.**

The AWS SDK for JavaScript v3 (`@aws-sdk/*`) is built on a deterministic middleware stack architecture that processes every request through a defined pipeline. According to the `aws/agent-toolkit-for-aws` repository, you can implement middleware in AWS SDK for JavaScript v3 to perform request logging, inject custom headers, or implement retry logic by leveraging the `middlewareStack` property available on every client instance.

## Understanding the Middleware Stack Architecture

When you create a client (e.g., `S3Client`, `DynamoDBClient`), it receives an empty `middlewareStack` that defines a five-step execution pipeline. As documented in [`plugins/aws-core/skills/aws-sdk-js-v3-usage/SKILL.md`](https://github.com/aws/agent-toolkit-for-aws/blob/main/plugins/aws-core/skills/aws-sdk-js-v3-usage/SKILL.md), the steps run in the following order:

1. **initialize** – Prepare the command input before serialization.
2. **serialize** – Convert the input into an HTTP request.
3. **build** – Add headers, query strings, and other request components.
4. **finalizeRequest** – Make final adjustments before sending, such as request signing.
5. **deserialize** – Convert the HTTP response back into a typed output.

Each middleware function receives three arguments: `next` (the downstream handler), `context` (containing `commandName`, `clientName`, `logger`, and custom fields), and `args` (containing `input`, `request`, and other step-specific data). You register middleware using `client.middlewareStack.add()`, which accepts a handler function and a configuration object specifying the target step and execution order.

## Implementing Middleware in AWS SDK for JavaScript v3

The following examples demonstrate how to implement middleware in AWS SDK for JavaScript v3 for common use cases, referencing patterns from the `aws/agent-toolkit-for-aws` source code.

### Basic Logging Middleware (Build Step)

Add logging to inspect command inputs and outputs after the request is built but before signing. This example from [`plugins/aws-core/skills/aws-sdk-js-v3-usage/SKILL.md`](https://github.com/aws/agent-toolkit-for-aws/blob/main/plugins/aws-core/skills/aws-sdk-js-v3-usage/SKILL.md) shows the standard pattern:

```javascript
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";

// Create a client – typically outside a Lambda handler for container reuse
const client = new S3Client({ region: "us-east-1" });

// Add a middleware that logs the command name and its input
client.middlewareStack.add(
  (next, context) => async (args) => {
    console.log("Running:", context.commandName, "with input:", args.input);
    const result = await next(args);               // Call the next middleware
    console.log("Result:", result);                // Optional post‑processing
    return result;
  },
  {
    name: "LogInputMiddleware",
    step: "build",           // Run after the request is built but before signing
    override: true,
  }
);

// Use the client as usual – the middleware runs automatically
await client.send(new GetObjectCommand({ Bucket: "my-bucket", Key: "my-key" }));

```

### Adding Custom Headers (Initialize Step)

Inject headers or modify the input object before the SDK serializes it into an HTTP request. Using the **initialize** step ensures your logic runs first in the pipeline:

```javascript
client.middlewareStack.add(
  (next, context) => async (args) => {
    // Mutate the input before the SDK serializes it
    args.input.CustomHeader = "my‑value";
    return next(args);
  },
  {
    name: "CustomHeaderMiddleware",
    step: "initialize",    // Runs first – before serialization
    priority: "low",
  }
);

```

### Conditional Retry Logic (FinalizeRequest Step)

Implement custom retry strategies by wrapping the `next` call in a try-catch block within the **finalizeRequest** step. This executes after request signing but before the network call:

```javascript
client.middlewareStack.add(
  (next, context) => async (args) => {
    try {
      return await next(args);
    } catch (err) {
      if (err.$metadata?.httpStatusCode === 429) {
        // Simple exponential back‑off for throttling
        await new Promise(r => setTimeout(r, 200));
        return next(args);        // Retry once
      }
      throw err;
    }
  },
  {
    name: "ThrottleRetryMiddleware",
    step: "finalizeRequest",
    priority: "high",
  }
);

```

### Creating Reusable Middleware Factories (TypeScript)

For larger applications, export middleware as reusable functions using the `Pluggable` interface from `@aws-sdk/types`. This pattern allows you to share middleware across multiple clients:

```typescript
import type { Pluggable } from "@aws-sdk/types";

export const createTimingMiddleware = (label: string): Pluggable => ({
  applyToStack: (clientStack) => {
    clientStack.add(
      (next, ctx) => async (args) => {
        const start = Date.now();
        const out = await next(args);
        console.log(`${label} took ${Date.now() - start}ms`);
        return out;
      },
      { name: `${label}Timing`, step: "deserialize", override: true }
    );
  },
});

// Usage
client.middlewareStack.use(createTimingMiddleware("S3GetObject"));

```

## Middleware Configuration and Ordering

When you implement middleware in AWS SDK for JavaScript v3, the second argument to `add()` accepts an object with the following properties, as detailed in [`plugins/aws-core/skills/aws-sdk-js-v3-usage/references/clients.md`](https://github.com/aws/agent-toolkit-for-aws/blob/main/plugins/aws-core/skills/aws-sdk-js-v3-usage/references/clients.md):

- **name** – A unique identifier used for later removal or debugging.
- **step** – The pipeline stage (`initialize`, `serialize`, `build`, `finalizeRequest`, or `deserialize`).
- **priority** – Controls relative order; use `"high"` to run earlier or `"low"` to run later within the same step.
- **override** – When set to `true`, replaces existing middleware with the same name.

The [`plugins/aws-core/skills/aws-sdk-js-v3-usage/references/effective-practices.md`](https://github.com/aws/agent-toolkit-for-aws/blob/main/plugins/aws-core/skills/aws-sdk-js-v3-usage/references/effective-practices.md) file provides guidance on endpoint middleware ordering, while [`plugins/aws-core/skills/aws-sdk-js-v3-usage/references/performance.md`](https://github.com/aws/agent-toolkit-for-aws/blob/main/plugins/aws-core/skills/aws-sdk-js-v3-usage/references/performance.md) discusses the `cacheMiddleware` flag and when to disable it if your custom middleware conflicts with caching. For signature handling patterns, see [`plugins/aws-core/skills/aws-sdk-js-v3-usage/references/sigv4a.md`](https://github.com/aws/agent-toolkit-for-aws/blob/main/plugins/aws-core/skills/aws-sdk-js-v3-usage/references/sigv4a.md).

## Summary

- The AWS SDK for JavaScript v3 processes requests through five sequential steps: **initialize**, **serialize**, **build**, **finalizeRequest**, and **deserialize**.
- Use `client.middlewareStack.add()` with a handler signature `(next, context) => async args => { ... }` to inject logic at any step.
- Configure middleware with `name`, `step`, `priority`, and `override` options to control execution order and enable removal.
- Middleware is **client-wide**, automatically applying to all commands issued by the client instance.

## Frequently Asked Questions

### What are the five steps in the AWS SDK for JavaScript v3 middleware pipeline?

The pipeline consists of **initialize** (prepare input), **serialize** (convert to HTTP request), **build** (add headers and query strings), **finalizeRequest** (signing and final adjustments), and **deserialize** (parse response). These steps execute sequentially for every command sent through the client.

### How do I ensure my middleware runs before or after other middleware?

Use the **priority** option set to `"high"` or `"low"` when calling `client.middlewareStack.add()`. Higher priority middleware executes earlier within the same step. The **override** flag can also force replacement of existing middleware with the same name, as documented in [`plugins/aws-core/skills/aws-sdk-js-v3-usage/references/clients.md`](https://github.com/aws/agent-toolkit-for-aws/blob/main/plugins/aws-core/skills/aws-sdk-js-v3-usage/references/clients.md).

### Can I remove middleware after adding it to the stack?

Yes. Because you assign a unique **name** when adding middleware, you can call `client.middlewareStack.remove("YourMiddlewareName")` to disable it dynamically. This is useful for temporarily disabling logging or debugging logic in production environments.

### Does middleware affect all commands or only specific ones?

Middleware is **client-wide**, meaning it automatically runs for **all** commands issued by that client instance. If you need middleware for only specific operations, you must either check `context.commandName` inside your handler or create separate client instances with different middleware configurations.