storage
storage copied to clipboard
Renaming/deleting a folder in Supabase Studio Storage dashboard leaves a ghost record in storage.prefixes table
Bug report
- [ ] I confirm this is a bug with Supabase, not with my own application.
- [ ] I confirm I have searched the Docs, GitHub Discussions, and Discord.
Describe the bug
In local development, using Supabase Studio (latest version), if you create a folder and then rename it, the old folder will remain, and a new folder is created instead. So you end up with 2 folders. When this happens, you can delete the new folder, but you can't delete the old folder. This means that the bucket can't be deleted either.
Additionally a related problem is when creating nested folders (at least 2 levels deep) with at least 2 folders in each level. The parent folder of these folders will not be deleted.
I think there may be more nuances to this problem, but this is the main ones that I can replicate.
The work around is to remove the record from the storage.prefixes table using SQL editor.
To Reproduce
Steps to reproduce the behavior, please provide code snippets or a repository:
To reproduce this behavior by renaming:
- Create a bucket called "Bucket 1"
- Create a folder inside "Bucket 1" called "Folder 1"
- Rename "Folder 1" to "Folder 2"
- Delete "Folder 2" (Folder 2 is deleted)
- Delete "Folder 1" (Folder 1 is not deleted)
- Try to delete "Bucket 1" (Will get an error)
- See Error
To reproduce this behavior by creating at least 2 levels deep of folder structure:
- Create a bucket called "Bucket 1"
- Create a folders in the following structure:
- folder 1
- folder 1-1
- folder 1-1-1
- folder 1-1-2
- folder 1-2
- folder 1-2-1
- folder 1-2-2
- folder 1-1
- Delete "folder 1" (All folders will be deleted except folder 1)
- Try to delete "Bucket 1" (Will get an error)
- See Error
Expected behavior
- Renaming a folder should remove the old folder and its record from the storage.prefixes table.
- Deleting a root folder of nested folder should delete all records from the storage.prefixes table.
Screenshots
System information
- OS: macOS 13.7.4
- Browser: Chrome Version 134.0.6998.89 (Official Build) (x86_64)
- Version of supabase-js: N/A
- Version of Node.js: N/A
- Dashboard Version: Unsure how to get version but I'm using the latest version as of Marc 18 2025
Additional context
I came across this problem when I had complex folder structures, and I tried to delete the root folder, and noticed that alot of the folders were ghosted and I couldn't remove the bucket. I then noticed that those folders were still in the storage.prefixes table.
Hi @lhengl ,
In my local environment, I noticed that in the definition of the storage.objects table:
This means that both insert and update operations trigger objects_insert_prefix_trigger, which inserts records into the prefixes table. However, when renaming a folder, the old record remains in the prefixes table, leading to a “ghost directory”.
Currently, my workaround is to introduce a new objects_update_prefix_trigger:
CREATE OR REPLACE FUNCTION storage.objects_update_prefix_trigger()
RETURNS TRIGGER AS $$
DECLARE
old_prefixes TEXT[];
BEGIN
-- Ensure this is an update operation and the name has changed
IF TG_OP = 'UPDATE' AND NEW.name <> OLD.name THEN
-- Retrieve old prefixes
old_prefixes := storage.get_prefixes(OLD.name);
-- Remove old prefixes that are only used by this object
FOR i IN 1..array_length(old_prefixes, 1) LOOP
IF NOT EXISTS (
SELECT 1 FROM storage.objects
WHERE bucket_id = OLD.bucket_id
AND name <> OLD.name
AND name LIKE (old_prefixes[i] || '%')
) THEN
DELETE FROM storage.prefixes
WHERE bucket_id = OLD.bucket_id AND name = old_prefixes[i];
END IF;
END LOOP;
-- Add new prefixes
PERFORM storage.add_prefixes(NEW.bucket_id, NEW.name);
END IF;
-- Set the new level
NEW.level := storage.get_level(NEW.name);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Remove the old update trigger
DROP TRIGGER IF EXISTS objects_update_create_prefix ON storage.objects;
-- Create a new trigger to ensure it runs BEFORE UPDATE
CREATE TRIGGER objects_update_create_prefix
BEFORE UPDATE ON storage.objects
FOR EACH ROW
WHEN (NEW.name <> OLD.name)
EXECUTE FUNCTION storage.objects_update_prefix_trigger();
However, I’m not entirely sure whether this modification might introduce other issues.
Hi all, I've moved this across due to a PR being opened in the storage repo.
Closing this issue as it has been fixed