diff --git a/plugins/tasks/views/manage/manage-panel.tsx b/plugins/tasks/views/manage/manage-panel.tsx
index 0d0d544ad8..83528d4cca 100644
--- a/plugins/tasks/views/manage/manage-panel.tsx
+++ b/plugins/tasks/views/manage/manage-panel.tsx
@@ -440,11 +440,13 @@ function FolderRow({
   rootFolders,
   onRename,
   onMove,
+  onDelete,
 }: {
   folder: Folder;
   rootFolders: Folder[];
   onRename: (name: string) => Promise<void>;
   onMove: (parentFolderId: string | null) => Promise<void>;
+  onDelete: () => void;
 }) {
   const [renaming, setRenaming] = useState(false);
   const [draftName, setDraftName] = useState(folder.name);
@@ -524,6 +526,15 @@ function FolderRow({
           >
             <Icon name="Edit" className="size-3.5" />
           </Button>
+          <Button
+            size="icon"
+            variant="ghost"
+            className="size-6 text-muted-foreground hover:text-destructive"
+            aria-label={`Delete folder ${folder.name}`}
+            onClick={onDelete}
+          >
+            <Icon name="Trash2" className="size-3.5" />
+          </Button>
         </>
       )}
     </div>
@@ -533,7 +544,9 @@ function FolderRow({
 function FoldersSection() {
   const rpc = useTasksRpc();
   const folders = useFolders();
+  const projects = useProjects();
   const [error, setError] = useState<string | null>(null);
+  const [confirmDelete, setConfirmDelete] = useState<Folder | null>(null);
   const folderList = folders.data ?? [];
   // The sidebar nests folders one level deep, so only roots can be parents.
   const rootFolders = useMemo(
@@ -550,6 +563,29 @@ function FoldersSection() {
     }
   };
 
+  // Deleting a folder only unfiles what it held: the schema's ON DELETE SET
+  // NULL moves its projects and subfolders to the top level. Nothing else is
+  // removed, so the confirmation names the move rather than warning about loss.
+  function describeDeleteImpact(folder: Folder): string {
+    const projectCount = (projects.data ?? []).filter(
+      (project) => project.folderId === folder.id,
+    ).length;
+    const subfolderCount = folderList.filter(
+      (entry) => entry.parentFolderId === folder.id,
+    ).length;
+    const moved = [
+      projectCount > 0
+        ? `${projectCount} project${projectCount > 1 ? "s" : ""}`
+        : null,
+      subfolderCount > 0
+        ? `${subfolderCount} subfolder${subfolderCount > 1 ? "s" : ""}`
+        : null,
+    ].filter((part) => part !== null);
+    return moved.length === 0
+      ? "The folder is empty."
+      : `${moved.join(" and ")} move to the top level. No tasks are deleted.`;
+  }
+
   return (
     <div className="space-y-3">
       {folderList.length === 0 ? (
@@ -576,6 +612,10 @@ function FoldersSection() {
                   }),
                 )
               }
+              onDelete={() => {
+                setError(null);
+                setConfirmDelete(folder);
+              }}
             />
           ))}
         </div>
@@ -585,6 +625,22 @@ function FoldersSection() {
           {error}
         </p>
       ) : null}
+      <ConfirmDialog
+        open={confirmDelete !== null}
+        onOpenChange={(open) => {
+          if (!open) setConfirmDelete(null);
+        }}
+        title={`Delete folder “${confirmDelete?.name ?? ""}”?`}
+        description={confirmDelete ? describeDeleteImpact(confirmDelete) : ""}
+        confirmLabel="Delete folder"
+        destructive
+        onConfirm={() => {
+          const target = confirmDelete;
+          if (target) {
+            void run(() => rpc.call("deleteFolder", { folderId: target.id }));
+          }
+        }}
+      />
     </div>
   );
 }
diff --git a/plugins/tasks/views/manage/manage.test.tsx b/plugins/tasks/views/manage/manage.test.tsx
index 86beb529d3..c2177b03b8 100644
--- a/plugins/tasks/views/manage/manage.test.tsx
+++ b/plugins/tasks/views/manage/manage.test.tsx
@@ -750,6 +750,78 @@ describe("PresetDialog environment section", () => {
   });
 });
 
+describe("Manage folders", () => {
+  const parentFolder = {
+    id: "01HZZZZZZZZZZZZZZZZZZZZZF1",
+    name: "bb",
+    parentFolderId: null,
+    createdAt: "2026-07-15T00:00:00.000Z",
+  };
+  const childFolder = {
+    id: "01HZZZZZZZZZZZZZZZZZZZZZF2",
+    name: "archive",
+    parentFolderId: parentFolder.id,
+    createdAt: "2026-07-15T00:00:00.000Z",
+  };
+
+  function renderFolders(overrides: Record<string, unknown> = {}) {
+    return renderSlot(
+      app.navPanels[0]!,
+      { subPath: "manage" },
+      {
+        rpc: {
+          listProjects: () => ({
+            projects: [{ ...project, folderId: parentFolder.id }],
+          }),
+          listFolders: () => ({ folders: [parentFolder, childFolder] }),
+          listPresets: () => ({ presets: [] }),
+          sidebarSummary: () => ({ projects: [] }),
+          listTasks: () => ({ tasks: [] }),
+          listLabels: () => ({ labels: [] }),
+          ...overrides,
+        },
+      },
+    );
+  }
+
+  it("deletes a folder after naming what the delete unfiles", async () => {
+    const deleteCalls: Array<Record<string, unknown>> = [];
+    const slot = renderFolders({
+      deleteFolder: (input: Record<string, unknown>) => {
+        deleteCalls.push(input);
+        return { deleted: true };
+      },
+    });
+    fireEvent.mouseDown(await slot.findByRole("tab", { name: "Folders" }));
+    fireEvent.click(
+      await slot.findByRole("button", { name: "Delete folder bb" }),
+    );
+
+    // Nothing is destroyed: the schema re-parents the folder's contents.
+    await slot.findByText(
+      "1 project and 1 subfolder move to the top level. No tasks are deleted.",
+    );
+    fireEvent.click(slot.getByRole("button", { name: "Delete folder" }));
+    await waitFor(() => expect(deleteCalls).toHaveLength(1));
+    expect(deleteCalls[0]).toMatchObject({ folderId: parentFolder.id });
+  });
+
+  it("surfaces a failed delete instead of silently closing", async () => {
+    const slot = renderFolders({
+      deleteFolder: () => {
+        throw new Error("Folder not found");
+      },
+    });
+    fireEvent.mouseDown(await slot.findByRole("tab", { name: "Folders" }));
+    fireEvent.click(
+      await slot.findByRole("button", { name: "Delete folder archive" }),
+    );
+    await slot.findByText("The folder is empty.");
+    fireEvent.click(slot.getByRole("button", { name: "Delete folder" }));
+    await slot.findByRole("alert");
+  });
+});
+
 describe("NewProjectDialog", () => {
   function renderEmptyState(overrides: Record<string, unknown> = {}) {
     return renderSlot(
