QUESTPIE
ConceptsFields

Boolean field

f.boolean() is a true/false field backed by a Postgres boolean column, with a z.boolean() schema and equality / null filtering built in.

f.boolean() stores a true / false value. It produces a Postgres boolean column, derives a z.boolean() schema, and exposes equality and null filter operators on the typed where clause. It takes no arguments and has no type-specific methods, you shape it entirely with the shared modifiers every field has.

Prerequisites: read Fields first, this page covers only the boolean type. The f proxy, the chain-modifier model, and shared modifiers like .required() / .default() / .localized() are taught there.

What it does

  • Stores a true/false value, a Postgres boolean column, no precision or length to configure.
  • Validates as a boolean, the derived schema is z.boolean(), so a non-boolean insert is a validation error.
  • Filters with boolean operators, eq, ne, isNull, isNotNull, all typed against boolean.
  • Renders a toggle / checkbox in the admin form.

Quick start

Use f.boolean() inside a .fields() callback. There is no constructor argument; chain the shared modifiers to refine it.

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

export const posts = collection("posts").fields(({ f }) => ({
  published: f.boolean().label("Published").default(false).required(), // NOT NULL, defaults to false
  featured: f.boolean(),                                               // nullable boolean
}));

That gives posts a published column typed boolean on read, required on insert with a false default, and a nullable featured column, filterable with the operators below, with no schema or migration written by hand.

Constructor argument

f.boolean() takes no arguments. It always produces a boolean column with a z.boolean() schema.

CallColumnSchema
f.boolean()booleanz.boolean()

Chained methods

f.boolean() has no type-specific methods, it returns a plain Field, not a FieldWithMethods wrapper, because there is nothing boolean-specific to refine. Shape it with the shared modifiers every field carries (.required(), .default(), .label(), .description(), .localized(), .array(), .access(), .hooks(), …).

collection("settings").fields(({ f }) => ({
  // Common pattern: a non-null flag with an explicit default.
  emailNotifications: f
    .boolean()
    .label("Email notifications")
    .default(true)
    .required(),
}));

`.default(value)` is type-checked against `boolean`

.default() is type-constrained to the field's data type, exactly like Drizzle, f.boolean().default("yes") won't compile. Passing a .default(...) also makes the field optional on insert. For a column-level SQL default instead of a JS value, use .drizzle((c) => c.default(sql`true`)).

Filtering, operators

boolean uses the boolean operator set (booleanOps), so every boolean field is filterable with these in a where clause. The operand is typed boolean.

OperatorOperandMatches
eq / nebooleanExact equal / not equal.
isNull / isNotNullbooleanColumn is (not) null. Pass true to assert the condition, false to invert it.
// Only published rows
const { docs } = await app.collections.posts.find({
  where: { published: { eq: true } },
});

// Rows where `featured` was never set
const { docs: unset } = await app.collections.posts.find({
  where: { featured: { isNull: true } },
});

There are no gt / lt / in style operators, for a tri-state value (true / false / unknown), keep the column nullable and filter isNull / isNotNull alongside eq.

For the full query language, combining field filters with AND / OR / NOT, pagination, and orderBy, see the query reference.

When to use it

  • f.boolean(), any two-state flag: published, featured, isActive, acceptedTerms, feature toggles, opt-ins.
  • Pair it with .default(false) / .default(true) + .required() for a non-null flag that never reads back as null, usually what you want for a toggle.
  • Leave it nullable (no .required()) when "not yet decided" is a meaningful third state, then filter with isNull / isNotNull.
  • For a fixed set of more than two states (e.g. draft / review / live), reach for f.select() instead of stacking booleans.

Multiple values

Chain .array() to store a list of booleans in a single jsonb column, and bound the list length with .minItems(n) / .maxItems(n). This is a shared modifier, not boolean-specific:

answers: f.boolean().array().maxItems(20), // boolean[] stored as jsonb

TypeScript

A boolean field contributes a boolean to the generated row, insert, and where types, no annotation needed. After questpie generate, pull the shapes off the collection or the app types:

type Post = typeof posts.$infer.select;
//   ^? { id: string; published: boolean; featured: boolean | null; ... }
import type { CollectionDoc, CollectionWhere } from "#questpie/types";

type Post = CollectionDoc<"posts">;        // { published: boolean; ... }
type PostFilter = CollectionWhere<"posts">; // { published?: { eq?: boolean; ... } }

.required() makes the field non-null and required on insert; without it the column is nullable and the insert field optional. .default(false) makes the input optional and is type-checked against boolean. See Fields → inferred types for how modifiers flow into the generated types.

  • Fields, the f proxy, the chain-modifier model, and the shared modifiers (.required(), .default(), .array(), .localized(), …).
  • f.select(), when a flag grows into a fixed set of more than two states.
  • Validation, the auto-derived Zod schema and the .zod() escape hatch.
  • Relations, the full query language for combining the operators above.

On this page