laurent22/joplin · warning · Error

Cannot move notebook to this location

Error message

Cannot move notebook to this location

What it means

Thrown by Folder.moveToFolder() when canNestUnder() returns false. canNestUnder walks the target's ancestor chain and rejects moves that would create a cycle (moving a folder into itself or one of its descendants), plus moving into the conflict folder, a root shared folder, or the same folder. The error message is localized via _().

Source

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

		if (isRootSharedFolder(folder)) return false;

		const conflictFolderId = Folder.conflictFolderId();
		if (folderId === conflictFolderId || targetFolderId === conflictFolderId) return false;

		if (!targetFolderId) return true;

		while (true) {
			const folder = await Folder.load(targetFolderId);
			if (!folder.parent_id) break;
			if (folder.parent_id === folderId) return false;
			targetFolderId = folder.parent_id;
		}

		return true;
	}

	public static async moveToFolder(folderId: string, targetFolderId: string) {
		if (!(await this.canNestUnder(folderId, targetFolderId))) throw new Error(_('Cannot move notebook to this location'));

		const original = await this.load(folderId);
		const modifiedFolder: FolderEntity = {
			id: folderId,
			parent_id: targetFolderId,

			// When moving a note to a different folder, the user timestamp is not updated.
			// However updated_time is updated so that the note can be synced later on.
			updated_time: time.unixMs(),
			share_id: original.share_id,
		};

		const wasShared = !!modifiedFolder.share_id;
		const movedToTopLevel = original.parent_id !== '' && targetFolderId === '';
		if (wasShared && movedToTopLevel) {
			// When a shared subfolder is converted to a toplevel folder, clear its share_id
			// as soon as possible. Without this, modifiedFolder would be incorrectly treated
			// as a root shared folder by some logic.

View on GitHub (pinned to 2654b33620)

Solutions

  1. Call Folder.canNestUnder(folderId, targetFolderId) first and surface a user-friendly message if it returns false.
  2. Block the UI action when the target is the folder itself or one of its descendants (compute descendants once).
  3. Disallow dropping onto system folders (conflict, shared root, trash).
  4. Reload the folder tree if it may be stale, then retry the validation.

Example fix

// before
await Folder.moveToFolder(folderId, targetFolderId);
// after - pre-validate and inform the user
if (!(await Folder.canNestUnder(folderId, targetFolderId))) {
  throw new Error('That notebook cannot be moved into its own descendant.');
}
await Folder.moveToFolder(folderId, targetFolderId);
Defensive patterns

Strategy: validation

Validate before calling

if (!(await Folder.canNestUnder(folderId, targetFolderId))) {
  throw new Error('Move rejected: target would create a cycle or is a system folder.');
}

Type guard

function isInvalidMove(e: any): boolean {
  return e && typeof e.message === 'string' && /move notebook to this location/i.test(e.message);
}

Try / catch

try {
  await Folder.moveToFolder(folderId, targetFolderId);
} catch (e) {
  if (isInvalidMove(e)) {
    // inform user, or pick a different target
    return { moved: false, reason: 'cycle-or-system-folder' };
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling moveToFolder(folderId, targetFolderId) where targetFolderId is folderId itself, or a descendant of folderId, or the conflict folder, or a root shared folder — i.e. any placement that breaks the folder tree invariant.

Common situations: Drag-and-drop UI where the drop target is a subfolder of the dragged folder; programmatic re-parenting without cycle checks; attempting to nest under the conflict or shared-root system folder; race where the folder was moved elsewhere concurrently.

Related errors


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