Skip to content

feat(tus): return created object id via X-Supabase-Object-Id response header (closes #647) - #1500

Open
61465 wants to merge 1 commit into
supabase:masterfrom
61465:feat/tus-object-id-response-header-647
Open

61465 wants to merge 1 commit into
supabase:masterfrom
61465:feat/tus-object-id-response-header-647

Conversation

@61465

@61465 61465 commented Oct 10, 2026

Copy link
Copy Markdown

What

Fixes #647 — TUS clients
had no way to resolve the storage row they just wrote without issuing a
follow-up list or search request.

Maintainer @fenos pre-approved the pattern on the issue in 2024:

Yes! we could send the object_id back via a header like the upload-id.
totally a legit enhancement

Change

In onUploadFinish (src/http/routes/tus/lifecycle.ts):

- await uploader.completeUpload({ ... })
+ const completed = await uploader.completeUpload({ ... })

- return { headers: { 'Tus-Complete': '1' } }
+ const responseHeaders: Record<string, string> = { 'Tus-Complete': '1' }
+ if (completed?.obj?.id) {
+   responseHeaders['X-Supabase-Object-Id'] = String(completed.obj.id)
+ }
+ return { headers: responseHeaders }
  • Header name follows the existing kebab-case convention
    (Tus-Complete, Upload-ID).
  • Prefixed with X-Supabase- so it is unambiguously distinct from TUS
    protocol headers.
  • Gracefully omitted if completeUpload returns without an id — the
    header is never emitted as the string "undefined".

Tests

Two new vitest cases in src/http/routes/tus/lifecycle.test.ts:

  1. includes X-Supabase-Object-Id when the created object id is available
    — happy path with a UUID.
  2. omits X-Supabase-Object-Id gracefully when completeUpload returns no id
    — defensive fallback.

Both use the existing createRawTusRequest helper + spies on Uploader.prototype.completeUpload,
so they run without needing storage infrastructure.

CORS note

Verified src/app.ts:25 — CORS is handled by Kong upstream (// kong should take care of cors). No Access-Control-Expose-Headers change needed in
the app itself; operators who front the service without Kong already
configure header exposure at the reverse proxy.

Impact

  • Backwards compatible — adds a header, removes nothing. Older clients
    that don't read it see no change.
  • Zero behavior change for failure paths. The header is only set when
    completed.obj.id is non-empty.
  • Removes one round-trip from every TUS client flow that needs to look up
    the object after upload — common in file-manager UIs, workflow
    orchestrators, and S3-to-TUS bridges.

… header (closes supabase#647)

TUS clients had no way to resolve the row they just wrote without issuing a
follow-up list / search request. Maintainer @fenos pre-approved the pattern
("we could send the object_id back via a header like the upload-id") on the
issue in 2024.

- src/http/routes/tus/lifecycle.ts
  * Capture `completed.obj.id` from `uploader.completeUpload(...)`.
  * Return it as `X-Supabase-Object-Id` alongside the existing
    `Tus-Complete: 1` header. Header name mirrors Supabase's kebab-case
    convention and avoids collision with `Upload-ID` (which already carries
    the TUS resource id, not the storage row id).
  * Fallback: if `completed.obj.id` is unavailable (older code paths or
    error-recovery), the header is simply omitted — never undefined-stringified.

- src/http/routes/tus/lifecycle.test.ts
  * Two new vitest cases:
    - includes `X-Supabase-Object-Id` on happy path with a UUID
    - gracefully omits the header when `completeUpload` returns no id
  * `completeUpload` is spied; backend / location are stubbed via the
    existing `createRawTusRequest` helper (no infra required).

Note: CORS is handled by Kong upstream in Supabase deployments, so no
Access-Control-Expose-Headers change is needed in the app itself.
@61465
61465 requested a review from a team as a code owner October 10, 2026 16:55

This branch has not been deployed

No deployments
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.

Missing object_id in resumable uploads response using TUS

1 participant