> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/honojs/hono/llms.txt
> Use this file to discover all available pages before exploring further.

# Validation

> Learn how to validate request data in Hono using the validator middleware, including validation targets, error handling, and custom validators.

## Overview

Hono provides built-in validation middleware to validate and transform incoming request data. The `validator` middleware can validate various parts of the request including JSON body, form data, query parameters, path parameters, headers, and cookies.

## The Validator Middleware

The validator middleware is a flexible system for validating request data:

From `src/validator/validator.ts:46-88`:

```typescript theme={null}
export const validator = <
  InputType,
  P extends string,
  M extends string,
  U extends ValidationTargetByMethod<M>,
  VF extends (
    value: unknown extends InputType ? ValidationTargets[U] : InputType,
    c: Context<any, P2>
  ) => any,
  E extends Env = any,
>(
  target: U,
  validationFunc: VF
): MiddlewareHandler<E, P, V, ExtractValidationResponse<VF>>
```

## Validation Targets

The validator can validate different parts of the request:

* `json` - Request body as JSON
* `form` - Form data (multipart or urlencoded)
* `query` - Query string parameters
* `param` - Path parameters
* `header` - Request headers
* `cookie` - Request cookies

From `src/validator/validator.ts:9-12`:

```typescript theme={null}
type ValidationTargetKeysWithBody = 'form' | 'json'
type ValidationTargetByMethod<M> = M extends 'get' | 'head'
  ? Exclude<keyof ValidationTargets, ValidationTargetKeysWithBody>
  : keyof ValidationTargets
```

<Note>
  GET and HEAD requests cannot validate `json` or `form` targets since these methods must not have a body.
</Note>

## Basic Usage

### Validating JSON Body

```typescript theme={null}
import { Hono } from 'hono'
import { validator } from 'hono/validator'

const app = new Hono()

app.post(
  '/users',
  validator('json', (value, c) => {
    const parsed = {
      name: value.name,
      email: value.email,
      age: parseInt(value.age)
    }
    
    if (!parsed.name || !parsed.email) {
      return c.text('Invalid data', 400)
    }
    
    return parsed
  }),
  async (c) => {
    const { name, email, age } = c.req.valid('json')
    return c.json({ message: 'User created', name, email, age })
  }
)
```

### Validating Query Parameters

```typescript theme={null}
app.get(
  '/search',
  validator('query', (value, c) => {
    return {
      q: value.q || '',
      page: parseInt(value.page) || 1,
      limit: parseInt(value.limit) || 10
    }
  }),
  (c) => {
    const { q, page, limit } = c.req.valid('query')
    return c.json({ query: q, page, limit })
  }
)
```

### Validating Path Parameters

```typescript theme={null}
app.get(
  '/users/:id',
  validator('param', (value, c) => {
    const id = parseInt(value.id)
    if (isNaN(id) || id <= 0) {
      return c.text('Invalid ID', 400)
    }
    return { id }
  }),
  (c) => {
    const { id } = c.req.valid('param')
    return c.json({ userId: id })
  }
)
```

### Validating Form Data

```typescript theme={null}
app.post(
  '/upload',
  validator('form', (value, c) => {
    const file = value.file
    const description = value.description
    
    if (!file || !(file instanceof File)) {
      return c.text('File is required', 400)
    }
    
    return { file, description }
  }),
  async (c) => {
    const { file, description } = c.req.valid('form')
    // Process file upload
    return c.json({
      message: 'File uploaded',
      filename: file.name,
      size: file.size
    })
  }
)
```

## Validation Function

The validation function receives the extracted value and the context:

From `src/validator/validator.ts:14-22`:

```typescript theme={null}
export type ValidationFunction<
  InputType,
  OutputType,
  E extends Env = {},
  P extends string = string,
> = (
  value: InputType,
  c: Context<E, P>
) => OutputType | TypedResponse | Promise<OutputType> | Promise<TypedResponse>
```

### Synchronous Validation

```typescript theme={null}
app.post(
  '/data',
  validator('json', (value, c) => {
    // Synchronous validation
    if (!value.name) {
      return c.text('Name is required', 400)
    }
    return { name: value.name }
  }),
  (c) => {
    const data = c.req.valid('json')
    return c.json(data)
  }
)
```

### Asynchronous Validation

```typescript theme={null}
app.post(
  '/users',
  validator('json', async (value, c) => {
    // Async validation - check if email exists
    const exists = await checkEmailExists(value.email)
    
    if (exists) {
      return c.text('Email already exists', 409)
    }
    
    return {
      email: value.email,
      name: value.name
    }
  }),
  async (c) => {
    const user = c.req.valid('json')
    // Create user
    return c.json(user)
  }
)
```

## Error Handling

### Returning Error Responses

Return a Response object to short-circuit the request:

```typescript theme={null}
app.post(
  '/data',
  validator('json', (value, c) => {
    if (!value.email || !value.email.includes('@')) {
      // Return error response - stops execution
      return c.json({ error: 'Invalid email' }, 400)
    }
    return value
  }),
  (c) => {
    // This handler won't execute if validation fails
    const data = c.req.valid('json')
    return c.json({ success: true, data })
  }
)
```

From `src/validator/validator.ts:162-170`:

```typescript theme={null}
const res = await validationFunc(value as never, c as never)

if (res instanceof Response) {
  return res as ExtractValidationResponse<VF>
}

c.req.addValidatedData(target, res as never)

return (await next()) as ExtractValidationResponse<VF>
```

### Throwing HTTPException

```typescript theme={null}
import { HTTPException } from 'hono/http-exception'

app.post(
  '/data',
  validator('json', (value, c) => {
    if (!value.name) {
      throw new HTTPException(400, { message: 'Name is required' })
    }
    return value
  }),
  (c) => {
    const data = c.req.valid('json')
    return c.json(data)
  }
)
```

### Malformed Data Errors

The validator automatically handles malformed data:

From `src/validator/validator.ts:94-103`:

```typescript theme={null}
case 'json':
  if (!contentType || !jsonRegex.test(contentType)) {
    break
  }
  try {
    value = await c.req.json()
  } catch {
    const message = 'Malformed JSON in request body'
    throw new HTTPException(400, { message })
  }
  break
```

## Integration with Validation Libraries

### Using Zod

```typescript theme={null}
import { z } from 'zod'
import { validator } from 'hono/validator'

const userSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().int().positive().optional()
})

app.post(
  '/users',
  validator('json', (value, c) => {
    const result = userSchema.safeParse(value)
    
    if (!result.success) {
      return c.json({ error: result.error.flatten() }, 400)
    }
    
    return result.data
  }),
  (c) => {
    const user = c.req.valid('json')
    // user is typed as { name: string; email: string; age?: number }
    return c.json(user)
  }
)
```

### Zod Validator Helper

```typescript theme={null}
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'

const userSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().int().positive().optional()
})

app.post(
  '/users',
  zValidator('json', userSchema),
  (c) => {
    const user = c.req.valid('json')
    // Fully typed based on schema
    return c.json(user)
  }
)
```

## Multiple Validators

Chain multiple validators for different targets:

```typescript theme={null}
app.post(
  '/api/posts/:id/comments',
  validator('param', (value, c) => {
    const id = parseInt(value.id)
    if (isNaN(id)) {
      return c.text('Invalid post ID', 400)
    }
    return { id }
  }),
  validator('json', (value, c) => {
    if (!value.text || value.text.length < 1) {
      return c.text('Comment text is required', 400)
    }
    return { text: value.text }
  }),
  async (c) => {
    const { id } = c.req.valid('param')
    const { text } = c.req.valid('json')
    
    // Create comment
    return c.json({ postId: id, text })
  }
)
```

## Validation Target Implementation

From `src/validator/validator.ts:89-160`, the validator extracts data based on target:

<CodeGroup>
  ```typescript Query Extraction theme={null}
  case 'query':
    value = Object.fromEntries(
      Object.entries(c.req.queries()).map(([k, v]) => {
        return v.length === 1 ? [k, v[0]] : [k, v]
      })
    )
    break
  ```

  ```typescript Param Extraction theme={null}
  case 'param':
    value = c.req.param() as Record<string, string>
    break
  ```

  ```typescript Header Extraction theme={null}
  case 'header':
    value = c.req.header()
    break
  ```

  ```typescript Cookie Extraction theme={null}
  case 'cookie':
    value = getCookie(c)
    break
  ```
</CodeGroup>

## Form Data Handling

Form data supports both multipart and urlencoded formats:

From `src/validator/validator.ts:105-142`:

```typescript theme={null}
case 'form': {
  if (
    !contentType ||
    !(multipartRegex.test(contentType) || urlencodedRegex.test(contentType))
  ) {
    break
  }

  let formData: FormData

  if (c.req.bodyCache.formData) {
    formData = await c.req.bodyCache.formData
  } else {
    try {
      const arrayBuffer = await c.req.arrayBuffer()
      formData = await bufferToFormData(arrayBuffer, contentType)
      c.req.bodyCache.formData = formData
    } catch (e) {
      let message = 'Malformed FormData request.'
      message += e instanceof Error ? ` ${e.message}` : ` ${String(e)}`
      throw new HTTPException(400, { message })
    }
  }

  const form: BodyData<{ all: true }> = Object.create(null)
  formData.forEach((value, key) => {
    if (key.endsWith('[]')) {
      ((form[key] ??= []) as unknown[]).push(value)
    } else if (Array.isArray(form[key])) {
      (form[key] as unknown[]).push(value)
    } else if (Object.hasOwn(form, key)) {
      form[key] = [form[key] as string | File, value]
    } else {
      form[key] = value
    }
  })
  value = form
  break
}
```

### Form Arrays

```typescript theme={null}
app.post(
  '/form',
  validator('form', (value, c) => {
    // Fields ending with [] are treated as arrays
    return {
      tags: value['tags[]'], // Array of values
      name: value.name // Single value
    }
  }),
  (c) => {
    const data = c.req.valid('form')
    return c.json(data)
  }
)
```

## Type Safety

Validators provide full type safety:

```typescript theme={null}
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'

const postSchema = z.object({
  title: z.string().min(1).max(100),
  content: z.string(),
  published: z.boolean().default(false),
  tags: z.array(z.string()).optional()
})

app.post(
  '/posts',
  zValidator('json', postSchema),
  (c) => {
    const post = c.req.valid('json')
    // post is typed as:
    // {
    //   title: string
    //   content: string
    //   published: boolean
    //   tags?: string[]
    // }
    return c.json(post)
  }
)
```

## Accessing Validated Data

Access validated data using `c.req.valid()`:

```typescript theme={null}
app.post(
  '/users',
  validator('json', (value) => ({
    name: value.name,
    email: value.email
  })),
  (c) => {
    // Access validated JSON data
    const user = c.req.valid('json')
    
    return c.json(user)
  }
)
```

## Custom Validators

Create reusable validator functions:

```typescript theme={null}
import { validator } from 'hono/validator'
import type { ValidationFunction } from 'hono/validator'

function emailValidator<P extends string>() {
  return validator('json', (value, c) => {
    const email = value.email
    
    if (!email || typeof email !== 'string') {
      return c.text('Email is required', 400)
    }
    
    if (!email.includes('@')) {
      return c.text('Invalid email format', 400)
    }
    
    return { email: email.toLowerCase() }
  })
}

app.post('/register', emailValidator(), (c) => {
  const { email } = c.req.valid('json')
  return c.json({ email })
})
```

## Best Practices

<Tip>
  * Use validation libraries like Zod for complex schemas
  * Return specific error messages to help API consumers
  * Validate and transform data in one step
  * Use TypeScript to ensure type safety
  * Chain validators for multiple targets (params, query, body)
  * Keep validators focused and reusable
</Tip>

<Warning>
  * GET and HEAD requests cannot validate `json` or `form` targets
  * Always handle validation errors with appropriate status codes
  * Don't trust client input - always validate server-side
</Warning>

## Validation Patterns

### Pagination Validation

```typescript theme={null}
app.get(
  '/items',
  validator('query', (value, c) => {
    const page = parseInt(value.page) || 1
    const limit = parseInt(value.limit) || 10
    
    if (page < 1) {
      return c.text('Page must be >= 1', 400)
    }
    
    if (limit < 1 || limit > 100) {
      return c.text('Limit must be between 1 and 100', 400)
    }
    
    return { page, limit }
  }),
  (c) => {
    const { page, limit } = c.req.valid('query')
    return c.json({ page, limit })
  }
)
```

### Conditional Validation

```typescript theme={null}
app.post(
  '/users',
  validator('json', (value, c) => {
    const data: any = {
      name: value.name,
      email: value.email
    }
    
    // Conditional validation
    if (value.age !== undefined) {
      const age = parseInt(value.age)
      if (isNaN(age) || age < 0) {
        return c.text('Invalid age', 400)
      }
      data.age = age
    }
    
    return data
  }),
  (c) => {
    const user = c.req.valid('json')
    return c.json(user)
  }
)
```

## Related

* [Handlers](/concepts/handlers) - Learn about request handlers
* [Middleware](/concepts/middleware) - Understand middleware execution
* [Context](/concepts/context) - Access request and response data
