Bug report
Describe the bug
The uploadToSignedUrl method in the storage client accepts fileOptions (which includes the metadata property), but the implementation completely ignores it. As a result, files uploaded via signed URLs do not have their custom metadata persisted.
This is inconsistent with the standard .upload() method, which correctly processes and attaches metadata to the request body/headers.
To Reproduce
Steps to reproduce the behavior:
- Create a private bucket named
test-bucket.
- Generate a signed upload URL for a specific path.
- Use
uploadToSignedUrl to upload a file with custom metadata.
- Inspect the file metadata (via the dashboard, SQL, or a database trigger).
Code Snippet:
const path = 'folder/image.jpg';
const { data: { token } } = await supabase.storage
.from('test-bucket')
.createSignedUploadUrl(path);
// Upload with custom metadata
const { data, error } = await supabase.storage
.from('test-bucket')
.uploadToSignedUrl(path, token, myFile, {
metadata: {
custom_id: '12345',
processed: true
}
});
// Result: The file is uploaded, but 'custom_id' and 'processed' are missing from the storage.objects metadata.
I verified this using a Postgres function triggered on INSERT to storage.objects. The system metadata (size, mimetype) is present, but the custom metadata passed in fileOptions is completely missing.
Postgres Trigger Code:
DECLARE
v_custom_field text;
BEGIN
-- Log the full metadata object
RAISE LOG 'Metadata: %', jsonb_pretty(NEW.metadata);
-- Attempt to grab the custom key
v_custom_field := NEW.metadata ->> 'custom_field';
-- v_custom_field is NULL here
...
Resulting Log Output:
The log shows that NEW.metadata only contains system fields. The custom_field is nowhere to be found.
"Metadata: {
"eTag": "\"ce344cb1535357b3567fa12bf196287d\"",
"size": 91955,
"mimetype": "image/jpeg",
"cacheControl": "max-age=undefined",
"lastModified": "2026-01-10T04:12:42.000Z",
"contentLength": 91955,
"httpStatusCode": 200
}"
Expected behavior
The uploadToSignedUrl method should behave like .upload(): it should serialize the options.metadata object and append it to the FormData (or headers) so that Supabase Storage persists it.
Code Analysis (Root Cause)
I reviewed the source code for StorageFileApi.ts.
In the working upload() method (specifically the uploadOrUpdate helper), the code explicitly checks for and appends metadata:
// valid implementation in uploadOrUpdate
if (metadata) {
body.append('metadata', this.encodeMetadata(metadata))
}
However, in uploadToSignedUrl, this logic is missing entirely. The options object is created but the metadata property is never accessed or used.
Current implementation of uploadToSignedUrl:
async uploadToSignedUrl(...) {
// ...
const options = { upsert: DEFAULT_FILE_OPTIONS.upsert, ...fileOptions }
if (typeof Blob !== 'undefined' && fileBody instanceof Blob) {
body = new FormData()
body.append('cacheControl', options.cacheControl as string)
// ❌ MISSING: No check for options.metadata here
body.append('', fileBody)
}
// ...
}
System information
- Windows 11
- Chrome 143
- supabase-js: 2.90.1
- Node: 22.18.0
Additional context
The fix is likely to copy the metadata handling logic from uploadOrUpdate into uploadToSignedUrl. Specifically, appending the metadata field to the FormData when it exists.
Bug report
Describe the bug
The
uploadToSignedUrlmethod in the storage client acceptsfileOptions(which includes themetadataproperty), but the implementation completely ignores it. As a result, files uploaded via signed URLs do not have their custom metadata persisted.This is inconsistent with the standard
.upload()method, which correctly processes and attaches metadata to the request body/headers.To Reproduce
Steps to reproduce the behavior:
test-bucket.uploadToSignedUrlto upload a file with custom metadata.Code Snippet:
I verified this using a Postgres function triggered on
INSERTtostorage.objects. The system metadata (size, mimetype) is present, but the custom metadata passed infileOptionsis completely missing.Postgres Trigger Code:
Resulting Log Output:
The log shows that
NEW.metadataonly contains system fields. Thecustom_fieldis nowhere to be found.Expected behavior
The
uploadToSignedUrlmethod should behave like.upload(): it should serialize theoptions.metadataobject and append it to theFormData(or headers) so that Supabase Storage persists it.Code Analysis (Root Cause)
I reviewed the source code for
StorageFileApi.ts.In the working
upload()method (specifically theuploadOrUpdatehelper), the code explicitly checks for and appends metadata:However, in
uploadToSignedUrl, this logic is missing entirely. Theoptionsobject is created but themetadataproperty is never accessed or used.Current implementation of
uploadToSignedUrl:System information
Additional context
The fix is likely to copy the metadata handling logic from
uploadOrUpdateintouploadToSignedUrl. Specifically, appending themetadatafield to the FormData when it exists.