laurent22/joplin · error · Error

Parent ID cannot be the same as ID

Error message

Parent ID cannot be the same as ID

What it means

Thrown by Folder.save when options.userSideValidation is true and the entity being saved has a non-empty o.id and o.parent_id that are strictly equal (packages/lib/models/Folder.ts:1093). The guard prevents a notebook from becoming its own parent, which would create a self-referencing cycle in the folder tree. It only runs for user-initiated saves; sync and other internal paths skip userSideValidation by design (see the comment above the method).

Source

Thrown at packages/lib/models/Folder.ts:1093

		}

		return Folder.save(modifiedFolder, { autoTimestamp: false });
	}

	// These "duplicateCheck" and "reservedTitleCheck" should only be done when a user is
	// manually creating a folder. They shouldn't be done for example when the folders
	// are being synced to avoid any strange side-effects. Technically it's possible to
	// have folders and notes with duplicate titles (or no title), or with reserved words.
	public static async save(o: FolderEntity, options: SaveOptions & { duplicateCheck?: boolean; reservedTitleCheck?: boolean; stripLeftSlashes?: boolean } = null) {
		if (!options) options = {};

		if (options.userSideValidation === true) {
			if (!('duplicateCheck' in options)) options.duplicateCheck = true;
			if (!('reservedTitleCheck' in options)) options.reservedTitleCheck = true;
			if (!('stripLeftSlashes' in options)) options.stripLeftSlashes = true;

			if (o.id && o.parent_id && o.id === o.parent_id) {
				throw new Error('Parent ID cannot be the same as ID');
			}
		}

		if (options.stripLeftSlashes === true && o.title) {
			while (o.title.length && (o.title[0] === '/' || o.title[0] === '\\')) {
				o.title = o.title.substr(1);
			}
		}

		// We allow folders with duplicate titles so that folders with the same title can exist under different parent folder. For example:
		//
		// PHP
		//     Code samples
		//     Doc
		// Java
		//     My project
		//     Doc

View on GitHub (pinned to 2654b33620)

Solutions

  1. At the call site, verify the chosen parent is not the folder itself before calling save (reject the move in the UI layer).
  2. If this fires unexpectedly, inspect the entity: print o.id and o.parent_id to find which code path assigned the self-reference.
  3. Ensure sync/import code does not set userSideValidation:true when it merely needs to persist a server-supplied tree (that path intentionally skips this check).
  4. Add a UI guard disabling 'move into self' / 'set parent to current notebook' in the notebook picker.

Example fix

// before
folder.parent_id = targetFolder.id;
await Folder.save(folder, { userSideValidation: true });

// after
if (folder.id && folder.id === targetFolder.id) {
  throw new Error('A notebook cannot be moved into itself');
}
folder.parent_id = targetFolder.id;
await Folder.save(folder, { userSideValidation: true });
Defensive patterns

Strategy: validation

Validate before calling

// Reject self-parenting before it reaches the model layer.
function isValidParent(folder: FolderEntity, newParentId: string | null | undefined): boolean {
  return !folder.id || !newParentId || folder.id !== newParentId;
}

if (!isValidParent(folder, folder.parent_id)) {
  throw new Error('A notebook cannot be its own parent');
}

Type guard

function hasSelfReference(o: { id?: string; parent_id?: string | null }): boolean {
  return Boolean(o.id) && Boolean(o.parent_id) && o.id === o.parent_id;
}

Try / catch

// Save is part of a user action — surface a clear message, don't swallow.
try {
  await Folder.save(folder, { userSideValidation: true });
} catch (error) {
  if (error.message === 'Parent ID cannot be the same as ID') {
    throw new Error('You cannot move a notebook into itself.');
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling Folder.save(folder, { userSideValidation: true }) (or BatchSave/saveUserIpc paths that set it) with a FolderEntity whose parent_id was set to its own id — e.g. moving/dragging a notebook into itself in the UI, or constructing a folder from a form where the parent selector defaulted to the folder's own id.

Common situations: Drag-and-drop UI bug that assigns the dropped folder's id as its new parent; an import/migration script copying parent_id onto the folder itself; a reducer that erroneously reuses an entity's id as parent_id on update; tests that build a folder and forget to clear/guard parent_id.

Related errors


AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12). Data as JSON: /api/errors/1fd55d2baf12841f. Report an issue: GitHub.