payloadcms/payload · error · Error
Cannot move folder into its own subfolder
Error message
Cannot move folder into its own subfolder
What it means
Thrown by validateNoCircularReference.checkAncestor when, walking up the proposed parent's chain, the traversal reaches the document being updated. Moving a node into one of its own descendants is rejected here rather than silently creating a loop.
Source
Thrown at packages/payload/src/hierarchy/hooks/collectionBeforeChange.ts:101
}
const fieldName = parentFieldName
async function checkAncestor(
ancestorId: number | string,
visitedNodes: Set<string> = new Set(),
): Promise<void> {
// Create unique key for this node
const nodeKey = `${collection.slug}:${ancestorId}`
// Check if we've visited this node before (true loop in the chain)
if (visitedNodes.has(nodeKey)) {
throw new Error(`Circular reference detected: the parent chain contains a loop`)
}
// If we've reached the current document, this means we're trying to move into a child
if (ancestorId === currentDocId) {
throw new Error('Cannot move folder into its own subfolder')
}
// Add this node to visited set
visitedNodes.add(nodeKey)
try {
const ancestor = (await req.payload.findByID({
id: ancestorId,
collection: collection.slug,
depth: 0,
req,
select: {
[fieldName]: true,
},
})) as JsonObject
const nextParent = ancestor?.[fieldName]
View on GitHub (pinned to 00c58b35c0)
Solutions
- On the client, disable drop targets that are descendants of the dragged node (compute ancestry first).
- Before save, call getAncestors on the proposed parent and reject if it contains the current id.
- For subtree moves, first detach the target from its parent (set parent null) then attach under the descendant.
- Reorder migrations to reparent bottom-up, never parent-into-child.
Example fix
// before
await payload.update({ collection: 'folders', id: aId, data: { parent: cId } }) // C is child of A
// after
const ancestors = await getAncestors({ collectionSlug: 'folders', id: cId, req })
if (ancestors.some(a => a.id === aId)) {
throw new Error('Cannot move into a descendant')
}
await payload.update({ collection: 'folders', id: aId, data: { parent: cId } }) Defensive patterns
Strategy: validation
Validate before calling
const ancestors = await getAncestors({ collectionSlug, id: proposedParentId, req })
if (ancestors.some((a) => String(a.id) === String(currentDocId))) {
throw new Error('proposed parent is a descendant')
} Type guard
function isDescendant(
ancestors: { id: string | number }[],
selfId: string | number,
): boolean {
return ancestors.some((a) => String(a.id) === String(selfId))
} Try / catch
try {
await payload.update({ collection, id, data })
} catch (err) {
if (err instanceof Error && /own subfolder/.test(err.message)) {
return { error: 'Cannot move a folder into its own subfolder' }
}
throw err
} Prevention
- Compute ancestry client-side and disable descendant drop targets.
- Detach a node from its parent before moving it under a descendant.
When it happens
Trigger: Updating a folder's parent to one of its own descendants (e.g. setting folder A's parent to folder C where C is a child of A).
Common situations: Drag-and-drop UI that allows dropping a folder into its own subfolder; bulk reparenting scripts that do not account for ancestry; misordered migration that reparents parents into children.
Related errors
- Document cannot be its own parent
- Circular reference detected: the parent chain contains a loo
- Collection ${collectionSlug} is not a hierarchy
- The folder "${data.name || originalDoc.name}" contains ${isF
- The folder "${data?.name || originalDoc.name}" must have fol
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/192b539cdd2a42ac.
Report an issue: GitHub.