# Boolean field (/docs/schema/fields/boolean)

---
title: Boolean field
description: "The two-state flag. One `boolean` column, a `z.boolean()` schema, five filter operators, and no arguments to get wrong."
kind: reference
package: questpie
---

| Surface               | Detail                                             |
| --------------------- | -------------------------------------------------- |
| Signature             | `f.boolean()`                                      |
| Arguments             | none, there is no second form of the call          |
| Column                | `boolean`                                          |
| Schema                | `z.boolean()`                                      |
| Filter operators      | the boolean set, five entries, [below](#filtering) |
| Admin form            | a checkbox, or a switch when you ask for one       |
| Admin cell            | a badge reading Yes or No                          |
| Type-specific methods | none, the shared chain is the whole surface        |

```ts title="src/questpie/server/collections/posts.ts"
import { collection } from "#questpie/factories";

export const posts = collection("posts").fields(({ f }) => ({
	published: f.boolean().default(false).required(),
	featured: f.boolean(),
}));
```

`published` is `NOT NULL` with a Postgres default of `false`, and the key is
optional on insert because the default fills it. `featured` is nullable, so it
reads back as `boolean | null` and models three states rather than two.

## Methods

The type declares no methods of its own. Three from the base chain carry the
weight here.

| Method         | Effect                                                          |
| -------------- | --------------------------------------------------------------- |
| `.required()`  | `NOT NULL`, and the key is required on insert                   |
| `.default(v)`  | Writes a column default, and makes the key optional on insert   |
| `.localized()` | Moves the column into `<collection>_i18n`, one value per locale |

`.label()`, `.description()`, `.access()`, `.hooks()` and the rest of the base
chain behave here exactly as on any other type.

<Callout type="info" title="`.default()` is checked against `boolean`">
	The argument is constrained to the field's own data type, so
	`f.boolean().default("yes")` does not compile. It accepts a literal, a
	factory, or a raw SQL expression. The value lands on the Postgres column, not
	on the insert payload.
</Callout>

## Filtering

Boolean carries `booleanOps`, five operators.

| Operator               | Operand              | Matches                                     |
| ---------------------- | -------------------- | ------------------------------------------- |
| `eq` / `ne`            | `boolean`            | Equal, not equal                            |
| `not`                  | `boolean`, or `null` | Not equal, or `IS NOT NULL` when given null |
| `isNull` / `isNotNull` | `boolean`            | Pass `false` and each inverts               |

A bare value is the shorthand for `eq`, and on a nullable boolean a bare `null`
compiles to `IS NULL`. Both short forms live in the generated where type rather
than beside it, and `.required()` drops `null` from it.

```ts
const { docs } = await app.collections.posts.find({
	where: { published: true, featured: { isNull: true } },
});
```

There is nothing else. No `in`, no `gt`, because a column with two values has
nothing to order or enumerate.

## In the admin

The form control is `BooleanField`, a checkbox. The list cell is `BooleanCell`,
a badge reading Yes for a truthy value and No for everything else.

`.admin()` is the field extension `@questpie/admin` registers, and one key on it
is boolean-specific.

| Key         | Type                     | Default      | Effect                                 |
| ----------- | ------------------------ | ------------ | -------------------------------------- |
| `displayAs` | `"checkbox" \| "switch"` | `"checkbox"` | Swaps the checkbox for a toggle switch |

```ts
emailNotifications: f.boolean().default(true).admin({ displayAs: "switch" }),
```

The rest of the keys on `.admin()` are the shared ones every field takes.

<Callout type="warn" title="`null` and `false` are indistinguishable">
	Both controls render `!!value`, so an unset boolean shows unchecked and its
	cell reads No. The change handler always writes `true` or `false`, which means
	the admin can never put `null` back once a person touches the control.
</Callout>

<Callout type="warn" title="The list filter sheet cannot filter a boolean">
	It switches on the field registry name, `boolean`, while its two-state branch
	tests for `checkbox` and `switch`. Neither matches, so the field falls through
	to the presence-only list, is empty and is not empty. Filter through the API
	instead.
</Callout>

A localized boolean gets the locale indicator beside its label, the same as any
other localized field.

## Lists

`.array()` replaces the column with `jsonb` holding a list of booleans and swaps
the five operators for the multi-value set.

```ts
answers: f.boolean().array().maxItems(20),
```

<Callout type="warn" title="The admin has no control for a list of booleans">
	`ArrayField` picks its item control from an allowlist of five type names, and
	`boolean` is not among them, so each item falls back to a text input. What
	that input produces is a string, which `z.array(z.boolean())` rejects on save.
</Callout>

## Types

A boolean contributes `boolean` to the row, insert and update shapes, and
nothing needs annotating.

```ts
type Post = typeof posts.$infer.select;
//   ^? { published: boolean; featured: boolean | null; ... }
```

`.required()` makes it non-null on read and required on insert. `.default()`
makes it optional on insert. Without either, the key is optional and the value
is `boolean | null`.

## Related

[Select](/docs/schema/fields/select) is where a flag belongs once it grows past
two states, and it narrows the read type to the literal union of its values.

[Arrays](/docs/schema/fields/array) covers what `.array()` does to reads, writes
and filters, and why the item control falls back to text.

[Reading and writing](/docs/schema/collections/crud) has the rest of the query
language, `AND`, `OR`, `NOT`, `orderBy` and pagination.

[Validation](/docs/schema/validation) covers the derived schema and `.zod()`,
the way to add a check the type does not carry.
