---
url: https://docs.forestfuture.dev/d1-record/guide/untrusted-input.md
description: >-
  Permit only the fields you want from a request, return a 400 for values that
  can't be used, and check request values before you query with them.
---

# Untrusted input

A request can send any fields it likes. Pick the ones you want before they reach a Model.

## Permitting fields

Only permit the fields you want from a request:

```ts
const permitted = Account.permit(await request.json(), "email", "handle");
const account = await Account.create(permitted);
```

::: warning TIP
`permit` can also read `FormData` or `URLSearchParams`.
:::

Permitted values are cast to their attribute’s types, so `"42"` becomes `42` for an integer. [Models](./models#casting-assigned-values) lists what each type accepts. A page number from a query string can go straight to [`paginate`](./querying#paginating), which casts it too.

::: rails Beyond Rails
Rails’ `params.permit` only filters keys. d1-record’s also casts each value, and checks that each name is an attribute.
:::

## Using a schema library

If you validate requests with a schema library such as [Zod](https://zod.dev) or [Valibot](https://valibot.dev), pass the parsed result to the Model:

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

const SignUp = z.object({ email: z.string(), handle: z.string().min(3) });

const fields = SignUp.parse(await request.json());
const account = await Account.create(fields);
```

## Returning a 400

A value that can’t be cast throws a `TypeError`, and a body that isn’t JSON throws a `SyntaxError`. Return a 400 for either:

```ts
export async function updateAccount(account: Account, request: Request): Promise<Response> {
  let permitted;
  try {
    permitted = Account.permit(await request.json(), "handle", "seats");
  } catch (error) {
    if (error instanceof SyntaxError || error instanceof TypeError) {
      return new Response("Bad request", { status: 400 });
    }
    throw error;
  }

  await account.update(permitted);
  return Response.json(account);
}
```

## Querying with request values

Check that a value is a string before you query with it. JSON can send `null`, and `findBy({ token: null })` matches rows whose token is NULL:

```ts
export async function accept(request: Request): Promise<Response> {
  const { token } = await request.json<{ token: string }>();
  if (typeof token !== "string" || token === "") {
    return new Response("Send the invitation's token", { status: 400 });
  }

  const invitation = await Invitation.findBy({ token });
  if (invitation === null) return new Response("No such invitation", { status: 404 });

  await invitation.update({ token: null, acceptedAt: new Date() });
  return new Response(`Welcome, ${invitation.email}`);
}
```

::: warning TIP
A value that can’t be cast matches nothing, so `whereNot({ id: "abc" })` excludes nothing.
:::
