PostgreSQL

Prisma Nested Create Returns Undefined: Include the Relation

Separate missing response fields from missing database rows, and prove that a nested write succeeded before retrying it or changing the schema.

Mahdi Ben RhoumaMahdi Ben Rhouma
7 min read

TL;DR#

A successful nested create can store the children while returning a parent object with no relation property. In the Prisma ORM 7 generated-client API, add include: { exercises: true } alongside data on the write, or alongside where on the read. Do not repeat the insert merely because workout.exercises is undefined. First read the same parent using its returned id and request the relation explicitly.

The original Stack Overflow report creates a workout and nested exercises, then logs an object containing only the workout's scalar fields. A separate lookup also omits the exercises. The answer identifies the missing result selection, rather than a broken relation or failed insert. The versioned Prisma select-fields documentation confirms that default queries return scalar fields and exclude relations.

This page uses the generated-client API documented for Prisma ORM 7, including findUnique and object-form include. The Prisma migration reference distinguishes that API from the newer fluent query syntax. Match examples to the client your application actually imports.

Why the write and its return value disagree#

The nested object under data.exercises.create specifies records to write. It does not also request those records in the response. The parent can therefore have a valid id and category, and its children can have the correct foreign key, while reading the absent JavaScript property produces:

text
undefined

That is a result-shape problem. It is different from the entire query returning no parent, and different again from requesting a relation and receiving an empty list. Keeping those cases separate prevents an unnecessary schema change from turning a working write into a second problem.

The relation-query documentation describes nested writes and nested reads separately. A write describes the change; include or select describes which related data the caller receives. Asking for more fields does not require adding another relation, changing its optionality, or migrating a database that already stores the intended association.

If schema validation actually reports Prisma's ambiguous-relation error, follow that separate diagnosis. Adding relation names is not a remedy for an omitted response property on a query that already succeeds.

Fix the selection on the call whose result you use#

1. Return the children from the nested create#

The following exported function accepts your application's configured Prisma client. Its model names follow the question: a Workout has a category and an exercises relation; each Exercise has a name, a set count and an optional hold time.

js
export async function createWorkout(prisma) {
  return prisma.workout.create({
    data: {
      category: 'Pull',
      exercises: {
        create: [
          { name: 'row', numSets: 4 },
          { name: 'dead hang', numSets: 2, holdTime: 20 },
        ],
      },
    },
    include: { exercises: true },
  });
}

The position of include matters: it is a sibling of data, not a field to insert into the workout. The function's resolved result contains the parent fields and its requested exercises. Existing client initialisation stays outside this helper; it does not open a new client for every workout.

Use this form when the caller immediately needs to render the new workout, return its exercises from an API, or inspect the created child ids. If the caller only needs the parent's id, omitting the relation remains a valid choice. The omission itself is not evidence of failure.

2. Request the relation again on a later read#

An include on a write does not configure subsequent queries. The read that supplies a later response needs its own selection:

js
export async function loadWorkout(prisma, workoutId) {
  const workout = await prisma.workout.findUnique({
    where: { id: workoutId },
    include: { exercises: true },
  });
 
  if (workout === null) {
    return null;
  }
 
  return workout;
}

Pass the id returned by the successful create. The question's hard-coded lookup of the first id can inspect an earlier workout once the database contains other rows. That can confuse diagnosis even after the include is correct: the code may be reading a different parent from the one just written.

For an existing write, run this read before trying another create. Repeating a successful insert creates additional records; it is not a way to repair the shape of the earlier result. If the write actually throws a unique-constraint failure on an autogenerated id, investigate that database error instead of treating it as a missing include.

Verify the rows before changing the schema#

The result-selection behaviour was reproduced on Prisma 7.10.1 and Node 26.7.0 on Windows, using a native SQLite database and the Prisma SQLite adapter. The fixture preserves the question's parent/list-child relationship and optional foreign key. This validates the generated client's result shape; it is not a PostgreSQL deployment test or a GraphQL resolver test.

The reproduction first inserted an unrelated workout, then created a second workout with nested exercises and no include. It counted the children using the new parent's id. The first read omitted the relation; the second read changed only the selection by adding include. The raw observations were:

text
create.exercises: undefined
children stored before include: 2
findUnique without include: undefined
findUnique with include count: 2
children stored after include: 2
after disconnect and reconnect: 2

The children already existed before the include was added. The second read did not write them, and disconnecting and reconnecting the client still found them. A separate create with include returned the children immediately; a later plain read once again omitted them. This is why an API mutation's response and a following detail-page query can appear inconsistent even when both use the same database.

For your existing data, use this read-only diagnostic helper. It distinguishes an absent parent from a present parent whose child count can be checked independently:

js
export async function inspectWorkout(prisma, workoutId) {
  const parent = await prisma.workout.findUnique({
    where: { id: workoutId },
  });
 
  if (parent === null) {
    return { parent: null };
  }
 
  const storedChildren = await prisma.exercise.count({
    where: { workoutId },
  });
 
  return { parent, storedChildren };
}

Keep diagnostic data separate from production records. If cleaning up a test fixture encounters a foreign-key failure when deleting a parent, follow the relation's deletion rules; changing return fields cannot change a database constraint.

Select a smaller nested response#

Returning every child field is not always necessary. A list screen may need only exercise names and the workout category. Use a nested selection, as documented in Prisma's field-selection examples:

js
export async function loadWorkoutSummary(prisma, workoutId) {
  return prisma.workout.findUnique({
    where: { id: workoutId },
    select: {
      category: true,
      exercises: {
        select: { name: true },
      },
    },
  });
}

The reproduction returned only the requested parent and child keys with this query. Do not put a top-level include beside this top-level select: the relation-query reference disallows both at the same level, and the control query was rejected. A nested select inside an included relation is a different, supported structure.

Distinguish an omitted list from an empty list#

The negative controls returned an empty array for an existing workout with no exercises when the relation was included, and null for a missing workout. An omitted relation instead remained undefined. Do not replace all of those outcomes with an empty array while debugging: doing so hides whether the query fetched the relation at all.

Once the explicit Prisma query returns the expected list, inspect the value at your API boundary. If a later mapper or resolver omits it, follow that value through the application rather than repeating the database write. Finish the diagnosis by checking the same parent id, the requested response fields and the stored children independently; only then change the layer that actually lost the data.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.