Skip to content

feat(tus): object id in Supabase-Object-Id response header - #1500

Merged
ferhatelmas merged 3 commits into
supabase:masterfrom
61465:feat/tus-object-id-response-header-647
Oct 14, 2026
Merged

ferhatelmas merged 3 commits into
supabase:masterfrom
61465:feat/tus-object-id-response-header-647

Conversation

@61465

@61465 61465 commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

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.

@61465
61465 requested a review from a team as a code owner October 10, 2026 16:55
61465 and others added 3 commits October 14, 2026 02:40
… 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.
Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
@ferhatelmas
ferhatelmas force-pushed the feat/tus-object-id-response-header-647 branch from d472193 to 82eeb27 Compare October 14, 2026 08:32
@ferhatelmas ferhatelmas changed the title feat(tus): return created object id via X-Supabase-Object-Id response header (closes #647) feat(tus): object id in Supabase-Object-Id response header Oct 14, 2026
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 38535875013

Coverage increased (+0.02%) to 85.352%

Details

  • Coverage increased (+0.02%) from the base build.
  • Patch coverage: 1 uncovered change across 1 file (26 of 27 lines covered, 96.3%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
src/http/routes/tus/lifecycle.ts 19 18 94.74%
Total (2 files) 27 26 96.3%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 15055
Covered Lines: 13300
Line Coverage: 88.34%
Relevant Branches: 9460
Covered Branches: 7624
Branch Coverage: 80.59%
Branches in Coverage %: Yes
Coverage Strength: 855.32 hits per line

💛 - Coveralls

@ferhatelmas
ferhatelmas merged commit ca25d79 into supabase:master Oct 14, 2026
33 checks passed
ferhatelmas added a commit that referenced this pull request Oct 14, 2026
## What kind of change does this PR introduce?

feat

## What is the current behavior?

Object id is added in #1500 but it's less useful without version id.

## What is the new behavior?

Add version id similarly.

## Additional context

Related to #1500
https://www.rfc-editor.org/rfc/rfc6648.html#section-3

Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
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

3 participants