laurent22/joplin · error · Error

The trash folder cannot be deleted

Error message

The trash folder cannot be deleted

What it means

Thrown by Folder.batchDelete() if the list of folder IDs to delete includes the trash folder ID. The trash folder is a system-managed, permanent container and must never be removed, so the driver hard-aborts before attempting the operation.

Source

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

			'notes': Note,
			'resources': Resource,
		};

		for (const tableName of ['folders', 'notes', 'resources']) {
			const ItemClass = tableNameToClasses[tableName];
			const rows = await this.db().selectAll(`SELECT id FROM ${tableName} WHERE share_id = ?`, [shareId]);
			const ids: string[] = rows.map(r => r.id);
			await ItemClass.batchDelete(ids, deleteOptions);
		}
	}

	public static async batchDelete(folderIds: string[], options: DeleteOptions): Promise<void> {
		options = {
			deleteChildren: true,
			...options,
		};

		if (folderIds.includes(getTrashFolderId())) throw new Error('The trash folder cannot be deleted');

		const toTrash = !!options.toTrash;

		const folders: FolderEntity[] = await Folder.loadItemsByIds(folderIds);
		if (!folders.length) return; // noop

		const actionLogger = ActionLogger.from(options.sourceDescription);
		actionLogger.addDescription(`folder titles: ${JSON.stringify(folders.map(folder => folder.title))}`);
		options.sourceDescription = actionLogger;

		if (options.deleteChildren) {
			const childrenDeleteOptions: DeleteOptions = {
				disableReadOnlyCheck: options.disableReadOnlyCheck,
				sourceDescription: actionLogger,
				deleteChildren: true,
				toTrash,
			};

View on GitHub (pinned to 2654b33620)

Solutions

  1. Filter the trash folder ID out of the deletion list before calling batchDelete.
  2. Use the dedicated emptyTrash() API to clear trash contents rather than deleting the trash folder.
  3. In UI flows, exclude system folders (trash, conflict, shared root) from multi-select.
  4. Audit custom scripts/plugins that enumerate folders for deletion.

Example fix

// before
await Folder.batchDelete(allFolderIds, { toTrash: false });
// after - exclude the trash folder
const deletable = allFolderIds.filter(id => id !== getTrashFolderId());
await Folder.batchDelete(deletable, { toTrash: false });
Defensive patterns

Strategy: validation

Validate before calling

const safeIds = folderIds.filter(id => id !== getTrashFolderId());
if (safeIds.length !== folderIds.length) {
  throw new Error('Refusing to delete: trash folder ID was in the list.');
}

Type guard

function isTrashDeleteBlocked(e: any): boolean {
  return e && typeof e.message === 'string' && e.message === 'The trash folder cannot be deleted';
}

Try / catch

try {
  await Folder.batchDelete(ids, opts);
} catch (e) {
  if (isTrashDeleteBlocked(e)) {
    ids = ids.filter(id => id !== getTrashFolderId());
    if (ids.length) await Folder.batchDelete(ids, opts);
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Programmatic batch deletion that passes getTrashFolderId() in the folderIds array — e.g. a select-all-and-delete UI flow, a script iterating all folders, or a sync-driven bulk delete that inadvertently includes the trash.

Common situations: Custom automation or a plugin deletes every folder; a 'select all' UI action didn't filter out the trash; refactored code that previously only handled user notebooks now sees the trash; emptying trash logic recursed into the trash folder itself.

Related errors


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