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
- At the call site, verify the chosen parent is not the folder itself before calling save (reject the move in the UI layer).
- If this fires unexpectedly, inspect the entity: print o.id and o.parent_id to find which code path assigned the self-reference.
- 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).
- 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
- In notebook picker / drag-drop UI, disable selecting the folder being moved as its own destination.
- Validate parent_id !== id in form/reducer code before constructing the FolderEntity to be saved.
- Keep internal sync/import code paths from setting userSideValidation: true — that flag exists specifically to gate these user-side checks.
- When copying fields between folder objects, never blindly copy another folder's id into parent_id.
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
- Notebooks cannot be named "%s", which is a reserved title.
- The trash folder cannot be deleted
- Cannot move notebook to this location
- Cannot find "%s".
- Cannot find "%s".
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/1fd55d2baf12841f.
Report an issue: GitHub.