# How to Validate Input Data for Medusa API Routes: A Complete Zod Implementation Guide

> Learn how to validate input data for Medusa API routes using Zod. Discover how Medusa's built-in middleware ensures type-safe request bodies for robust API handling.

- Repository: [Medusa/medusa](https://github.com/medusajs/medusa)
- Tags: how-to-guide
- Published: 2026-05-19

---

**Medusa validates API requests using Zod schemas defined in `*_validators.ts` files, processed by the `validateAndTransformBody` middleware from `@medusajs/framework/http`, which attaches parsed, type-safe data to `req.validatedBody` before the route handler executes.**

The `medusajs/medusa` repository implements a declarative validation architecture that ensures every API endpoint receives strictly typed, sanitized input. Rather than manual validation inside route handlers, Medusa separates schema definitions from business logic, leveraging Zod's strict parsing and transformation capabilities to reject malformed requests before they reach the service layer.

## The Validation Architecture

Medusa's validation flow follows a three-step pipeline implemented consistently across both store and admin API routes. According to the source code in `packages/medusa/src/api/store/carts`, the system relies on Zod for schema definition and custom framework middleware for execution.

### Step 1: Define Zod Schemas in `*_validators.ts` Files

Validation schemas reside in dedicated validator files alongside route definitions. In [`packages/medusa/src/api/store/carts/validators.ts`](https://github.com/medusajs/medusa/blob/main/packages/medusa/src/api/store/carts/validators.ts), the `CreateCart` schema demonstrates the pattern using Zod's fluent API with strict mode enabled.

Key characteristics of Medusa validators include:

- **`.strict()` configuration** – Rejects unknown keys to prevent payload bloat, as seen in line 28 of the cart validators file
- **`WithAdditionalData` wrappers** – Allow dynamic field injection while maintaining base schema integrity
- **Type coercion** – Automatically transforms string inputs to numbers, dates, or other types before validation completes

```typescript
// packages/medusa/src/api/store/carts/validators.ts
import { z } from "@medusajs/framework/zod"
import { WithAdditionalData } from "@medusajs/framework/types"

export const CreateCart = z
  .object({
    email: z.string().email(),
    region_id: z.string(),
    shipping_address: z.object({
      address_1: z.string(),
      city: z.string(),
      country_code: z.string().length(2),
      postal_code: z.string(),
    }),
    items: z.array(
      z.object({
        variant_id: z.string(),
        quantity: z.number().gt(0),
      })
    ),
  })
  .strict()

```

### Step 2: Attach Validation Middleware to Routes

The `validateAndTransformBody` middleware (or `validateAndTransformQuery` for GET requests) is registered in the route's middleware configuration array. In [`packages/medusa/src/api/store/carts/middlewares.ts`](https://github.com/medusajs/medusa/blob/main/packages/medusa/src/api/store/carts/middlewares.ts), this middleware intercepts incoming requests, parses the JSON body against the Zod schema, and handles validation failures by returning a 400 error with detailed field-level messages.

```typescript
// packages/medusa/src/api/store/carts/middlewares.ts
import {
  validateAndTransformBody,
  validateAndTransformQuery,
} from "@medusajs/framework/http"
import { CreateCart } from "./validators"

export const storeCartRoutesMiddlewares = [
  {
    method: ["POST"],
    matcher: "/store/carts",
    middlewares: [
      validateAndTransformBody(CreateCart),
    ],
  },
]

```

### Step 3: Consume Validated Data in Route Handlers

Once validation passes, the middleware attaches the parsed and typed data to `req.validatedBody`. Route handlers in files like [`packages/medusa/src/api/store/carts/route.ts`](https://github.com/medusajs/medusa/blob/main/packages/medusa/src/api/store/carts/route.ts) access this property, which is guaranteed to match the Zod schema's TypeScript type definition.

```typescript
// packages/medusa/src/api/store/carts/route.ts
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"

export const POST = async (
  req: MedusaRequest, 
  res: MedusaResponse
) => {
  // req.validatedBody is typed and validated according to CreateCart schema
  const cartData = req.validatedBody
  
  const cartService = req.scope.resolve("cartService")
  const cart = await cartService.create(cartData)
  
  return res.status(201).json({ cart })
}

```

## Validating Query Parameters for GET Requests

For endpoints that accept query strings, Medusa uses `validateAndTransformQuery` combined with `createSelectParams()` to handle pagination, field selection, and filtering. This pattern appears in the same middleware configuration files but targets `req.validatedQuery` instead of `req.validatedBody`.

```typescript
// packages/medusa/src/api/store/carts/validators.ts
import { createSelectParams } from "../../../utils/validators"

export const GetCartQueryParams = createSelectParams()

```

```typescript
// packages/medusa/src/api/store/carts/middlewares.ts
{
  method: ["GET"],
  matcher: "/store/carts/:id",
  middlewares: [
    validateAndTransformQuery(GetCartQueryParams),
  ],
}

```

The `createSelectParams()` utility generates a standard schema supporting `fields`, `limit`, `offset`, and `order` parameters used consistently across Medusa's API surface.

## Complete Implementation Example

Here is a complete workflow for validating input data in a custom orders endpoint:

**1. Define the schema:**

```typescript
// src/api/store/orders/validators.ts
import { z } from "@medusajs/framework/zod"

export const CreateOrderSchema = z
  .object({
    email: z.string().email(),
    shipping_address: z.object({
      address_1: z.string().min(1),
      city: z.string(),
      country_code: z.string().length(2),
      postal_code: z.string(),
    }),
    items: z.array(
      z.object({
        variant_id: z.string(),
        quantity: z.number().gt(0),
      })
    ),
  })
  .strict()

```

**2. Configure middleware:**

```typescript
// src/api/store/orders/middlewares.ts
import { validateAndTransformBody } from "@medusajs/framework/http"
import { CreateOrderSchema } from "./validators"

export const storeOrderRoutesMiddlewares = [
  {
    method: ["POST"],
    matcher: "/store/orders",
    middlewares: [
      validateAndTransformBody(CreateOrderSchema),
    ],
  },
]

```

**3. Implement the handler:**

```typescript
// src/api/store/orders/route.ts
import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework/http"

export const POST = async (
  req: AuthenticatedMedusaRequest,
  res: MedusaResponse
) => {
  const orderService = req.scope.resolve("orderService")
  
  // req.validatedBody is guaranteed to match CreateOrderSchema
  const order = await orderService.create(req.validatedBody)
  
  return res.status(201).json(order)
}

```

## Summary

- **Zod schemas** defined in `*_validators.ts` files describe the exact shape, types, and constraints of request payloads using `.strict()` mode to reject unknown properties.
- **`validateAndTransformBody`** middleware from `@medusajs/framework/http` executes validation automatically and attaches parsed data to `req.validatedBody`.
- **`validateAndTransformQuery`** handles GET request validation, typically using `createSelectParams()` for standardized pagination and field selection.
- **Source references**: The cart implementation in [`packages/medusa/src/api/store/carts/validators.ts`](https://github.com/medusajs/medusa/blob/main/packages/medusa/src/api/store/carts/validators.ts) and [`packages/medusa/src/api/store/carts/middlewares.ts`](https://github.com/medusajs/medusa/blob/main/packages/medusa/src/api/store/carts/middlewares.ts) demonstrates the canonical pattern used throughout the Medusa codebase.

## Frequently Asked Questions

### What happens if validation fails in a Medusa API route?

When Zod validation fails, the `validateAndTransformBody` middleware automatically returns a 400 Bad Request response with detailed error messages indicating which fields failed validation and why. This occurs before the route handler executes, ensuring that business logic only receives valid, sanitized data.

### Can I use custom validation logic beyond Zod schemas?

While Medusa's framework is optimized for Zod-based validation in `*_validators.ts` files, you can implement custom middleware functions that execute before or after the standard validation middleware. However, the recommended approach is to extend Zod schemas using `.refine()` or `.transform()` methods to keep validation declarative and type-safe.

### How do I validate request headers or route parameters in Medusa?

For route parameters like `:id` in `/store/orders/:id`, Medusa automatically validates that the parameter exists as a string. For custom header validation, you would create a custom middleware function registered in the route's middlewares array, similar to how `validateAndTransformBody` is attached, but targeting `req.headers` instead of `req.body`.

### Where does the validated data get stored in the request object?

After successful validation, the parsed data is attached to `req.validatedBody` for POST/PUT/PATCH requests or `req.validatedQuery` for GET requests. These properties are typed according to the Zod schema definition and are accessible within the route handler function without additional type casting.