# Data Validation in the TanStack Start Drizzle SaaS Kit

> Learn how to validate data in the TanStack Start Drizzle SaaS Kit.

*Canonical: https://makerkit.dev/docs/tanstack-drizzle/security/data-validation*

---

Data Validation is a crucial aspect of building secure applications. In this section, we will look at how to validate data in the TanStack Start Drizzle SaaS Kit.

{% sequence title="Steps to validate data" description="Learn how to validate data in the TanStack Start Drizzle SaaS Kit." %}

[What are we validating?](#what-are-we-validating)

[Using Zod to validate data](#using-zod-to-validate-data)

[Validating payload data to Server Side API](#validating-payload-data-to-server-side-api)

[Validating Cookies](#validating-cookies)

[Validating URL parameters](#validating-url-parameters)

{% /sequence %}

## What are we validating?

A general rule, is that all client-side provided data should be validated/sanitized. This includes:

- URL parameters
- Search params
- Form data
- Cookies
- Any data provided by the user

## Using Zod to validate data

The TanStack Start Drizzle SaaS Kit uses [Zod](https://zod.dev/) to validate data. You can use the `z` object to validate data in your application.

```ts
import { z } from "zod";

const userSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
});

// Validate the data
const validatedData = userSchema.parse(data);
```

## Validating payload data to Server Side API

We generally use **server functions** (`createServerFn`) for sending data from a client to a server. The TanStack Start Drizzle SaaS Kit applies the pre-composed middleware tuples from `@kit/function-middleware` to a literal `createServerFn(...)` call, which wires validation and authentication into the standard `createServerFn` chain.

### Server actions: Applying the middleware tuples

Apply `authFunctionMiddleware` to ensure the user is authenticated. Add `.validator(schema)` to validate input against a Zod schema and `.handler(...)` to implement the action.

```ts
import { createServerFn } from "@tanstack/react-start";
import { authFunctionMiddleware } from "@kit/function-middleware/functions";
import { z } from "zod";

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
});

export const createUser = createServerFn({ method: 'POST' })
  .middleware(authFunctionMiddleware)
  .validator(UserSchema)
  .handler(async ({ data, context }) => {
    // `data` is now validated against the UserSchema
    // `context.user` is the authenticated user

    // do something with the validated data
  });
```

When you define an action with `authFunctionMiddleware` and `.validator(...)`, the `data` argument is validated against the schema automatically, without you having to do anything else. In addition, `context.user` and `context.session` are available to you, which contain the authenticated user and session data.

Call the action from a client component with TanStack Query:

```ts
import { useMutation } from '@tanstack/react-query';

import { createUser } from './create-user.functions';

const mutation = useMutation({ mutationFn: createUser });

mutation.mutate({ data: { id, name, email } });
```

### Organization-scoped actions

For actions that require organization context, apply `organizationFunctionMiddleware`:

```ts
import { createServerFn } from "@tanstack/react-start";
import { organizationFunctionMiddleware } from "@kit/function-middleware/functions";
import { z } from "zod";

const UpdateDocumentSchema = z.object({
  documentId: z.string().uuid(),
  content: z.string(),
});

export const updateDocument = createServerFn({ method: 'POST' })
  .middleware(organizationFunctionMiddleware)
  .validator(UpdateDocumentSchema)
  .handler(async ({ data, context }) => {
    // `data` is validated
    // `context.user` is the authenticated user
    // `context.organizationId` is the current organization
    // `context.role` is the user's role in the organization

    // do something with the validated data
  });
```

### Actions without input

For actions that don't require input validation, omit `.validator(...)`:

```ts
import { createServerFn } from "@tanstack/react-start";
import { authFunctionMiddleware } from "@kit/function-middleware/functions";

export const getCurrentUser = createServerFn({ method: 'POST' })
  .middleware(authFunctionMiddleware)
  .handler(async ({ context }) => {
    // No input needed, just return user data
    return context.user;
  });
```

### Role-protected actions

For actions that require a minimum role level, layer the `withMinRole` middleware:

```ts
import { createServerFn } from "@tanstack/react-start";
import { organizationFunctionMiddleware } from "@kit/function-middleware/functions";
import { withMinRole } from "@kit/function-middleware/server";
import { z } from "zod";

const DeleteMemberSchema = z.object({
  memberId: z.string().uuid(),
});

export const deleteMember = createServerFn({ method: 'POST' })
  .middleware([...organizationFunctionMiddleware, withMinRole('admin')])
  .validator(DeleteMemberSchema)
  .handler(async ({ data, context }) => {
    // Only admins and owners can reach this point
    // `data` is validated
  });
```

### Permission-protected actions

For actions that require specific permissions, layer the `withFeaturePermission` middleware:

```ts
import { createServerFn } from "@tanstack/react-start";
import { organizationFunctionMiddleware } from "@kit/function-middleware/functions";
import { withFeaturePermission } from "@kit/function-middleware/server";
import { z } from "zod";

const UpdateBillingSchema = z.object({
  planId: z.string(),
});

export const updateBilling = createServerFn({ method: 'POST' })
  .middleware([...organizationFunctionMiddleware, withFeaturePermission({ billing: ['update'] })])
  .validator(UpdateBillingSchema)
  .handler(async ({ data, context }) => {
    // Only users with billing:update permission can reach this point
  });
```

## Validating Cookies

Whenever you use a cookie in your application, you should validate the cookie data.

On the server (inside a server function, loader, or middleware), read cookies with `getCookie` from `@tanstack/react-start/server`. Let's assume you receive a cookie with the name `my-cookie`, and you expect it to be a number. You can validate the cookie data as follows:

```ts
import { z } from "zod";
import { getCookie } from "@tanstack/react-start/server";

const cookie = z.coerce.number()
    .safeParse(getCookie("my-cookie"));

if (!cookie.success) {
  // handle the error or provide a default value
}
```

## Validating URL parameters

Whenever you receive a search parameter, you should validate it. TanStack Router validates search params at the route boundary with `validateSearch`, so the rest of your route only ever sees validated values. Let's assume you receive a search parameter named `page`, and you expect it to be a number:

```ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute('/items/')({
  validateSearch: (search: Record<string, unknown>) => ({
    page: search.page ? Number(search.page) : 0,
  }),
  loaderDeps: ({ search }) => ({ page: search.page }),
  loader: ({ deps }) => getItems({ data: deps.page }),
});
```

You can also validate with a Zod schema for stricter parsing and defaults:

```ts
import { createFileRoute } from "@tanstack/react-router";
import { z } from "zod";

const SearchSchema = z.object({
  page: z.coerce.number().min(0).catch(0),
});

export const Route = createFileRoute('/items/')({
  validateSearch: (search) => SearchSchema.parse(search),
});
```

In components, read the validated values with `Route.useSearch()`; for dynamic path segments use `Route.useParams()`. Because validation happens at the route boundary, your component receives already-validated, typed data.

Going forward, remember to validate all data that you receive from the client, and never trust anything the client provides you with. Always have a default value ready to handle invalid data (the `.catch(...)` above is a convenient way to do this), which can prevent potential security issues or bugs in your application.
