payloadcms/payload · error · Error

Document cannot be its own parent

Error message

Document cannot be its own parent

What it means

Thrown by the hierarchy beforeChange hook when, during an update, the parent field is being changed and the resolved new parent id equals the document's own id. Self-parenting would create an immediate cycle, so it is rejected outright.

Source

Thrown at packages/payload/src/hierarchy/hooks/collectionBeforeChange.ts:44

  ({ parentFieldName }: Args): CollectionBeforeChangeHook =>
  async ({ collection, data, operation, originalDoc, req }) => {
    // Determine the new parent ID
    const newParentID =
      data[parentFieldName] !== undefined ? data[parentFieldName] : originalDoc?.[parentFieldName]
    const parentChanged =
      operation === 'update' &&
      data[parentFieldName] !== undefined &&
      data[parentFieldName] !== originalDoc?.[parentFieldName]

    // Validate circular references when parent is changing
    if (parentChanged && newParentID) {
      // Extract parent ID (could be plain ID or populated object with id)
      const parentId =
        typeof newParentID === 'object' && 'id' in newParentID ? newParentID.id : newParentID

      // Prevent self-referential parent
      if (parentId === (originalDoc?.id || data.id)) {
        throw new Error('Document cannot be its own parent')
      }

      // Check for true circular references (loops in the chain), but allow
      // moving into a child - that will be handled by afterChange reparenting
      await validateNoCircularReference({
        collection,
        currentDocId: originalDoc?.id,
        parentId,
        req,
      })
    }

    return data
  }

/**
 * Walks up the parent chain to detect true circular references (loops).
 * Does NOT throw when moving into a child - that case is handled by afterChange

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. On the client, filter the current document out of the parent-selection options.
  2. Before save, strip or rewrite data[parentFieldName] when it equals the doc id.
  3. Validate in a beforeValidate hook and surface a user-friendly message.
  4. Audit import scripts to never set parent = id.

Example fix

// before
await payload.update({ collection, id, data: { parent: id } })

// after
if (data.parent === id) {
  delete data.parent // or throw a friendly validation error
}
await payload.update({ collection, id, data })
Defensive patterns

Strategy: validation

Validate before calling

if (data[parentFieldName] === id) {
  delete data[parentFieldName]
}

Type guard

function isSelfParent(
  id: string | number,
  parent: unknown,
): boolean {
  const pid =
    typeof parent === 'object' && parent !== null && 'id' in parent
      ? (parent as { id: string | number }).id
      : parent
  return String(pid) === String(id)
}

Try / catch

try {
  await payload.update({ collection, id, data })
} catch (err) {
  if (err instanceof Error && /own parent/.test(err.message)) {
    return { error: 'A folder cannot be its own parent' }
  }
  throw err
}

Prevention

When it happens

Trigger: PATCH/PUT or payload.update where data[parentFieldName] is set to the same value as the doc's own id, on a collection with hierarchy enabled.

Common situations: UI bug that prefills the parent picker with the current node; import/migration that copies a row's id into its parent column; client that posts the full doc including its existing parent id but shifts it to the self field.

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/fd7c650b14ecc563. Report an issue: GitHub.