beekeeper-studio/beekeeper-studio · error

Cannot delete folder "${this.name}" — move or remove its ${p

Error message

Cannot delete folder "${this.name}" — move or remove its ${pluralize('query', count, true)} first.

What it means

QueryFolder.preventRemoveIfNotEmpty is a TypeORM @BeforeRemove() hook that blocks deleting a query folder that still contains favorite queries. It counts FavoriteQuery rows with the folder's queryFolderId and throws when the count is greater than zero, forcing the user to move or delete the contained queries first.

Source

Thrown at apps/studio/src/common/appdb/models/QueryFolder.ts:41

  @Column({ type: 'integer', nullable: true, default: null })
  @PreventMovingFolderInsideItself
  parentId: Nullable<number> = null

  // Do NOT initialize this to null. A null initializer becomes an own property
  // that gets copied into transport objects by cls.merge(), and TypeORM treats an
  // explicitly-null relation as "unset this FK", overriding the parentId column.
  @ManyToOne(() => QueryFolder, { nullable: true, onDelete: 'SET NULL' })
  @JoinColumn({ name: 'parentId' })
  parent?: QueryFolder

  @OneToMany(() => FavoriteQuery, (query) => query.queryFolder)
  queries: FavoriteQuery[]

  @BeforeRemove()
  async preventRemoveIfNotEmpty(): Promise<void> {
    const count = await FavoriteQuery.countBy({ queryFolderId: this.id })
    if (count > 0) {
      throw new Error(`Cannot delete folder "${this.name}" — move or remove its ${pluralize('query', count, true)} first.`)
    }
  }

  @BeforeInsert()
  @BeforeUpdate()
  async preventDuplicateName(): Promise<void> {
    if (!this.name) return
    const where: any = {
      name: this.name,
      parentId: this.parentId ?? IsNull(),
    }
    if (this.id) where.id = Not(this.id)
    const existing = await QueryFolder.findOneBy(where)
    if (existing) {
      throw new Error(`A folder named "${this.name}" already exists in this location.`)
    }
  }
}

View on GitHub (pinned to 4e3e03e322)

Solutions

  1. Move the contained queries to another folder (update FavoriteQuery.queryFolderId) before removing the folder.
  2. Delete the FavoriteQuery rows in the folder first, then delete the folder.
  3. Catch the error in the UI and show the message so the user can empty the folder manually.

Example fix

// before
await queryFolderRepo.remove(folder);
// after
const count = await FavoriteQuery.countBy({ queryFolderId: folder.id });
if (count === 0) {
  await queryFolderRepo.remove(folder);
} else {
  await FavoriteQuery.update({ queryFolderId: folder.id }, { queryFolderId: otherFolder.id });
  await queryFolderRepo.remove(folder);
}
Defensive patterns

Strategy: validation

Validate before calling

const queryCount = await FavoriteQuery.countBy({ queryFolderId: folder.id });
if (queryCount > 0) {
  throw new Error(`Move or remove ${queryCount} quer${queryCount === 1 ? 'y' : 'ies'} before deleting this folder.`);
}

Try / catch

try {
  await queryFolderRepo.remove(folder);
} catch (e) {
  if (String(e.message).startsWith('Cannot delete folder')) {
    showError(e.message); // prompt user to empty the folder first
  }
}

Prevention

When it happens

Trigger: Calling folder.remove() / repository.remove(folder) / soft cascade deletes on a QueryFolder whose id is referenced by one or more FavoriteQuery.queryFolderId rows.

Common situations: Users trying to delete a folder via sidebar context menu while it still holds saved queries; cleanup scripts deleting folder rows without inspecting children; code assuming cascade delete that is not configured on the relation.

Related errors


AI-assisted analysis of beekeeper-studio/beekeeper-studio@4e3e03e322 (2026-08-31). Data as JSON: /api/errors/df7a5476b85574b3. Report an issue: GitHub.