Skip to main content

Select & Include Schemas

Two schema families mirror Prisma Client's projection arguments:

  • <Model>Select validates a select payload. It has one optional entry per readable model field, plus _count when the model has a list relation.
  • <Model>Include validates an include payload. It has one optional entry per relation field, plus _count.

Both are generated by default. You do not need any configuration to get them — you only need configuration to turn them off, or to run in a mode that suppresses them.

Files emitted

FileExportsEmitted when
objects/<Model>Select.schema.ts<Model>SelectObjectSchema (typed z.ZodType<Prisma.<Model>Select>) and <Model>SelectObjectZodSchemaSelect enabled
objects/<Model>Include.schema.ts<Model>IncludeObjectSchema (typed z.ZodType<Prisma.<Model>Include>) and <Model>IncludeObjectZodSchemaInclude enabled and the model has at least one relation to an enabled model
objects/<Model>Args.schema.ts<Model>ArgsObjectSchema and <Model>ArgsObjectZodSchema — a wrapper holding select, plus include when the model has a relationSelect or Include enabled
objects/<Model>CountOutputTypeSelect.schema.ts, objects/<Model>CountOutputTypeArgs.schema.ts, objects/<Model>CountOutputTypeCount<Relation>Args.schema.ts…ObjectSchema / …ObjectZodSchema pairs backing _countSelect enabled and the model has a list relation

<Model>Args and <Model>CountOutputTypeArgs are exported untyped: there is no matching type on the Prisma namespace to bind them to, so no z.ZodType<…> annotation is emitted. The typed/Zod export pair follows the usual rules described in dual exports.

Switching them off

Two spellings exist, in two different places.

In the JSON config file the keys are addSelectType and addIncludeType:

zod-generator.config.json
{
"addSelectType": false,
"addIncludeType": false
}

In the Prisma generator block the keys are isGenerateSelect and isGenerateInclude, and — like every generator-block option — their values are quoted strings:

generator zod {
provider = "prisma-zod-generator"
isGenerateSelect = "false"
isGenerateInclude = "false"
}

Generator-block values are parsed strictly: only "true" and "false" are accepted (case-insensitive, surrounding whitespace trimmed). Any other value fails generation with an Invalid generator option "isGenerateSelect" error.

Resolution order for each of the two switches:

  1. The generator-block flag (isGenerateSelect / isGenerateInclude), when present.
  2. The JSON config key (addSelectType / addIncludeType), when present.
  3. Enabled.

The two spellings are not interchangeable. isGenerateSelect inside the JSON config file is not read by select/include resolution, and addSelectType inside the generator block is not read either. See configuration precedence for how the rest of the options merge.

Minimal mode force-disables both

When the resolved mode is minimal, Select and Include are disabled regardless of either flag. This is a hard override, applied at three levels: the input types are never appended, the object schemas are pruned by the minimal-mode deny list (/Args$/, /Include$/, /Select$/), and relation entries in the inline select schemas degrade to plain booleans.

If a flag explicitly asked for them, the run logs a notice at info level (so it appears in normal Prisma CLI output, not only under debug logging):

Minimal mode active: Select schemas will be disabled even if enabled by legacy flags or config.
Minimal mode active: Include schemas will be disabled even if enabled by legacy flags or config.

The notice is emitted only for an explicit true — leaving both flags unset in minimal mode is silent. See generation modes.

Emitted shape

With the default zodImportTarget, entries in <Model>Select look like this:

Field kindEmitted entry
Scalar or enumname: z.boolean().optional()
List relationname: z.union([z.boolean(), z.lazy(() => <Related>FindManySchema)]).optional()
To-one relationname: z.union([z.boolean(), z.lazy(() => <Related>ArgsObjectSchema)]).optional()
_count_count: z.union([z.boolean(), z.lazy(() => <Model>CountOutputTypeArgsObjectSchema)]).optional()

<Model>Include uses the same two relation forms and omits scalars entirely. Its _count entry only gains the <Model>CountOutputTypeArgsObjectSchema alternative when Select is also enabled; with Include on and Select off, _count is z.boolean().optional().

Relation entries are a union of boolean and a nested args schema, not a bare boolean, so query arguments pass through a projection (fixed in 2.1.6, issue #387):

UserFindManySchema.parse({
select: {
id: true,
posts: { select: { id: true }, where: { title: { equals: 'x' } }, take: 5 },
},
});

UserFindManySchema.parse({
select: { _count: { select: { posts: { where: { title: { equals: 'x' } } } } } },
});

Plain booleans stay valid ({ select: { id: true, posts: false, _count: true } }), and non-conforming values are still rejected — { select: { posts: 5 } } and { select: { posts: { bogus: 1 } } } both fail.

Three details are worth knowing about the nested reference:

  • For a list relation the reference is the related model's root operation schema (objects/<Model>Select.schema.ts imports <Related>FindManySchema from ../findMany<Related>.schema), not a separate args object schema.
  • <Model>Include and the inline select schemas substitute <Related>ArgsObjectSchema when findMany is disabled for the related model by operation filtering. objects/<Model>Select.schema.ts does not make that substitution.
  • With zodImportTarget: "v4" the same entry is emitted as a getter — get posts(){ return z.union([z.boolean(), PostFindManySchema]).optional(); } — so the reference is resolved on first parse instead of at module scope. Strictness is rendered as z.strictObject({ … }) under that target. See strict mode and zod import targets.

A relation entry is only added when Transformer.isModelEnabled returns true for the related model:

  • In <Model>Select, a relation field pointing at a disabled model stays in the schema but degrades to z.boolean().optional() — the nested args alternative is dropped.
  • In <Model>Include, relation fields pointing at disabled models are dropped from the schema entirely. If that leaves the model with no enabled relation, no objects/<Model>Include.schema.ts is written at all.
  • _count is added only when the model has a list relation to an enabled model.

Both families are additionally gated per model: nothing is emitted unless the model is enabled and at least one of findUnique, findFirst, or findMany is enabled for it.

Field-level filtering

<Model>Select is built from the model's fields filtered through the result variant, so a field excluded from result (via globalExclusions.result or a per-model variants.result.excludeFields) has no entry. See hiding fields.

The inline copies described below are built from the model's datamodel fields directly and do not apply that filter, so an excluded field still appears there as a boolean entry.

Where operation schemas pick them up

findFirst, findFirstOrThrow, and findMany inline their select schema into the operation file, to keep the circular import between a model's select schema and its own findMany schema out of the module graph:

generated/schemas/findManyUser.schema.ts
// Select schema needs to be in file to prevent circular imports
//------------------------------------------------------

export const UserFindManySelectSchema: z.ZodType<Prisma.UserSelect> = /* … */;
export const UserFindManySelectZodSchema = /* … */;

The inline copy is emitted for those three operations unconditionally; with Select disabled it is still there, boolean-only. include is always referenced from objects/<Model>Include.schema.ts, never inlined.

Every other operation that accepts a projection references the standalone object schemas, so the select / include keys disappear from the operation schema when the corresponding switch is off: findUnique, findUniqueOrThrow, createOne, deleteOne, updateOne, upsertOne (both keys), and createManyAndReturn, updateManyAndReturn (select only).

Worked example

schema.prisma
model User {
id Int @id @default(autoincrement())
email String @unique
posts Post[]
}

model Post {
id Int @id @default(autoincrement())
title String
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}

With no select/include configuration (both default to enabled) and default strict mode, the select schema for User is:

generated/schemas/objects/UserSelect.schema.ts
const makeSchema = () =>
z.object({
id: z.boolean().optional(),
email: z.boolean().optional(),
posts: z.union([z.boolean(), z.lazy(() => PostFindManySchema)]).optional(),
_count: z.union([z.boolean(), z.lazy(() => UserCountOutputTypeArgsObjectSchema)]).optional(),
}).strict();

export const UserSelectObjectSchema: z.ZodType<Prisma.UserSelect> =
makeSchema() as unknown as z.ZodType<Prisma.UserSelect>;
export const UserSelectObjectZodSchema = makeSchema();

Import lines are omitted above; PostFindManySchema comes from ../findManyPost.schema and UserCountOutputTypeArgsObjectSchema from ./UserCountOutputTypeArgs.schema.

Post has a to-one relation and no list relation, so its select schema uses the Args form and has no _count:

generated/schemas/objects/PostSelect.schema.ts
    id: z.boolean().optional(),
title: z.boolean().optional(),
author: z.union([z.boolean(), z.lazy(() => UserArgsObjectSchema)]).optional(),
authorId: z.boolean().optional(),

The include schema for User keeps only the relation and _count:

generated/schemas/objects/UserInclude.schema.ts
    posts: z.union([z.boolean(), z.lazy(() => PostFindManySchema)]).optional(),
_count: z.union([z.boolean(), z.lazy(() => UserCountOutputTypeArgsObjectSchema)]).optional(),

The supporting _count schemas emitted alongside them are objects/UserCountOutputTypeArgs.schema.ts (a wrapper with a single select key), objects/UserCountOutputTypeSelect.schema.ts (posts: z.union([z.boolean(), z.lazy(() => UserCountOutputTypeCountPostsArgsObjectSchema)]).optional()), and objects/UserCountOutputTypeCountPostsArgs.schema.ts (where: z.lazy(() => PostWhereInputObjectSchema).optional()) — which is what makes the filtered-count payload in the previous section validate.

For the surrounding pipeline steps, see object and CRUD generation.