Skip to content

fix(storage): include metadata in uploadToSignedUrl request#2155

Open
MaxwellCalkin wants to merge 3 commits intosupabase:masterfrom
MaxwellCalkin:fix/upload-to-signed-url-metadata
Open

fix(storage): include metadata in uploadToSignedUrl request#2155
MaxwellCalkin wants to merge 3 commits intosupabase:masterfrom
MaxwellCalkin:fix/upload-to-signed-url-metadata

Conversation

@MaxwellCalkin
Copy link
Copy Markdown

Summary

Fixes supabase/storage#823.

uploadToSignedUrl accepts metadata in fileOptions but the implementation never reads or appends it to the request. Files uploaded via signed URLs silently drop custom metadata, unlike .upload() which works correctly.

Root cause: The uploadToSignedUrl method constructs the request body without checking options.metadata, while the uploadOrUpdate helper (used by .upload() and .update()) includes metadata handling for all three body types.

Fix: Copy the metadata handling pattern from uploadOrUpdate into uploadToSignedUrl:

  • Blob body: append metadata field to FormData
  • FormData body: append metadata field (with !body.has() guard, matching uploadOrUpdate)
  • Raw body (string/buffer/stream): set x-metadata base64 header

Also forwards fileOptions.headers to the request, matching uploadOrUpdate behavior.

Test plan

  • Added 3 unit tests covering metadata in all body type branches (Blob, FormData, raw body)
  • All existing mocked unit tests continue to pass
  • Build passes (nx build storage-js)
  • Integration tests require Docker (unchanged)

Transparency note: This PR was authored by Claude Opus 4.6 (an AI), directed by a human supervisor. See our project page for context.

The uploadToSignedUrl method accepted metadata in fileOptions but never
used it. This copies the metadata handling from uploadOrUpdate into
uploadToSignedUrl for all three body types (Blob, FormData, raw body).

Also forwards custom fileOptions.headers, matching uploadOrUpdate behavior.

Fixes supabase/storage#823
@MaxwellCalkin MaxwellCalkin requested review from a team as code owners March 8, 2026 15:54
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 26291052-a0ae-4c74-8d36-6f2ebda923bd

📥 Commits

Reviewing files that changed from the base of the PR and between 71d1ab9 and 9556d72.

📒 Files selected for processing (1)
  • packages/core/storage-js/src/packages/StorageFileApi.ts

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • File uploads now support metadata across payloads: metadata is added to FormData for Blob/FormData uploads and sent as a base64 header for raw body uploads. Improved option/header merging and enhanced streaming support ensure uploads work reliably (including Node.js fetch/stream scenarios).
  • Tests

    • Added tests verifying metadata propagation for Blob, FormData, and raw body upload paths and correct header/option handling.

Walkthrough

The PR updates StorageFileApi to merge DEFAULT_FILE_OPTIONS before per-call options, capture fileOptions.metadata, and propagate metadata across upload paths. For Blob/FormData uploads metadata is added to FormData; for raw bodies metadata is base64-encoded and sent as an x-metadata header. Per-call headers are merged into base headers and duplex is forwarded/inferred for streaming bodies. Tests were added for uploadToSignedUrl to verify metadata handling for Blob, FormData, and raw body uploads.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client (storage-js)
  participant SignedURL as Signed URL endpoint
  participant Storage as Storage Server

  Client->>Client: Merge DEFAULT_FILE_OPTIONS + fileOptions\ncapture metadata
  alt Blob or File
    Client->>Client: create FormData\nappend file, cacheControl, metadata (if present)
    Client->>SignedURL: PUT FormData (with metadata field)
  else FormData passed in
    Client->>Client: ensure cacheControl present\nappend metadata if missing
    Client->>SignedURL: PUT FormData (with metadata field)
  else Raw body/stream
    Client->>Client: preserve headers\nadd x-metadata: base64(metadata) if present\nset duplex for streams
    Client->>SignedURL: PUT body with headers (including x-metadata)
  end
  SignedURL->>Storage: forward upload
  Storage->>SignedURL: store object with metadata
  SignedURL-->>Client: response
Loading

Assessment against linked issues

Objective Addressed Explanation
Support metadata in uploadToSignedUrl fileOptions parameter [#823]
Serialize and propagate metadata through FormData and headers [#823]
Align uploadToSignedUrl behavior with upload() method [#823]

Out-of-scope changes

Code Change Explanation
Add duplex inference/forwarding for streaming bodies (packages/core/storage-js/src/packages/StorageFileApi.ts) Duplex handling is not part of the linked issue which only requested metadata propagation; this introduces additional behavior for stream uploads.
Change merge order to spread DEFAULT_FILE_OPTIONS before fileOptions (packages/core/storage-js/src/packages/StorageFileApi.ts) Reordering default option merging affects overall option precedence beyond metadata concerns and is not specified in the linked issue.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/storage-js/src/packages/StorageFileApi.ts`:
- Around line 282-296: The uploadToSignedUrl path fails to set fetch duplex for
stream bodies; update the uploadToSignedUrl function to detect Node readable
streams the same way uploadOrUpdate does and, when the body is a stream, pass
duplex: 'half' through to the put call (i.e., include { headers, duplex: 'half'
} or merge into the options argument you pass into put(this.fetch,
url.toString(), body, options)), ensuring the same stream detection/handling
logic and propagation used by uploadOrUpdate is reused for consistency.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 20624c43-b34f-4b37-a09e-fbb8d7b7a7a0

📥 Commits

Reviewing files that changed from the base of the PR and between c338e2f and 71d1ab9.

📒 Files selected for processing (2)
  • packages/core/storage-js/src/packages/StorageFileApi.ts
  • packages/core/storage-js/test/storageFileApi.test.ts

The raw-body path in uploadToSignedUrl was missing the stream detection
and duplex: 'half' propagation that uploadOrUpdate already has. Without
this, passing a ReadableStream or Node.js stream to uploadToSignedUrl
fails on Node 20+ fetch which requires duplex: 'half' for stream bodies.

Mirrors the exact pattern from uploadOrUpdate (lines 121-142) into
uploadToSignedUrl for consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 9, 2026

Open in StackBlitz

@supabase/auth-js

npm i https://pkg.pr.new/@supabase/auth-js@2155

@supabase/functions-js

npm i https://pkg.pr.new/@supabase/functions-js@2155

@supabase/postgrest-js

npm i https://pkg.pr.new/@supabase/postgrest-js@2155

@supabase/realtime-js

npm i https://pkg.pr.new/@supabase/realtime-js@2155

@supabase/storage-js

npm i https://pkg.pr.new/@supabase/storage-js@2155

@supabase/supabase-js

npm i https://pkg.pr.new/@supabase/supabase-js@2155

commit: 25a5bad

Copy link
Copy Markdown
Contributor

@mandarini mandarini left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall it's a solid PR. Let's remove this redundant line, and I will approve, after verifying with storage team. Thank you for the contribution!

const headers: Record<string, string> = {
const options = {
...DEFAULT_FILE_OPTIONS,
upsert: DEFAULT_FILE_OPTIONS.upsert,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove this line as it's redundant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

uploadToSignedUrl ignores metadata in fileOptions (inconsistent with .upload())

2 participants