Skip to content

fix(storage): keep sortBy defaults when list() is given a partial sortBy - #2454

Merged
mandarini merged 1 commit into
supabase:masterfrom
i-anubhav-anand:fix/storage-list-sortby-defaults
Jun 18, 2026
Merged

fix(storage): keep sortBy defaults when list() is given a partial sortBy#2454
mandarini merged 1 commit into
supabase:masterfrom
i-anubhav-anand:fix/storage-list-sortby-defaults

Conversation

@i-anubhav-anand

@i-anubhav-anand i-anubhav-anand commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

🔍 Description

What changed?

StorageFileApi.list() shallow-spread its defaults into the request body, so a partial sortBy dropped the sibling default. This deep-merges sortBy so a partial override keeps the default for whichever key you don't set.

Why was this change needed?

The case that actually fails today is order-only: list('folder', { sortBy: { order: 'desc' } }). On master this sends a sortBy with no column, and the Storage server requires column, so it returns a 400. With the deep merge, column falls back to 'name' and the request succeeds. (The symmetric column-only case likewise keeps the default order: 'asc'.)

📸 Examples / Proof

test/list-sortby-defaults.test.ts (Docker-free, custom-fetch capture). On master the order-only and column-only cases fail; with this change all pass:

  • { sortBy: { order: 'desc' } }{ column: 'name', order: 'desc' } (was { order: 'desc' } → server 400)
  • { sortBy: { column: 'updated_at' } }{ column: 'updated_at', order: 'asc' }
  • no options → { column: 'name', order: 'asc' }

🔄 Breaking changes

  • This PR contains no breaking changes

📋 Checklist

  • I have read the Contributing Guidelines
  • My PR title follows conventional commits: fix(storage): ...
  • I have run pnpm nx format
  • I have added tests
  • Documentation (not needed)

📝 Additional notes

Rebased on master. pnpm nx build storage-js passes; the full storage integration suite (Docker) runs in CI.

@i-anubhav-anand
i-anubhav-anand requested review from a team as code owners June 16, 2026 14:36
@mandarini mandarini self-assigned this Jun 17, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jun 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

@supabase/auth-js

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

@supabase/functions-js

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

@supabase/postgrest-js

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

@supabase/realtime-js

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

@supabase/storage-js

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

@supabase/supabase-js

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

commit: bf8a2da

@mandarini mandarini left a comment

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.

Hi @i-anubhav-anand, thank you so much for contributing to Supabase!

Nice catch on the shallow spread dropping the nested sortBy default, and thank you for the Docker-free test that captures the request body directly. That makes it really easy to reason about.

One thing worth knowing: for the exact case in the description (sortBy: { column: 'updated_at' }), the storage server already defaults order to 'asc' when it is missing, so the result ordering is actually the same with or without this change for that example. Where your fix genuinely matters is the symmetric case: list('folder', { sortBy: { order: 'desc' } }). On master that sends sortBy with no column, and the server requires column, so it comes back as a 400. With your deep merge, column falls back to 'name' and the request succeeds. That is the real bug this fixes.

Could you add a test for that order-only case? It is the scenario that actually fails today, and it would make the value of the change obvious. It would also be great to lead the description with it.

Also, can you please rebase with master to fix your CI?

Thank you again for taking the time on this!!

list() shallow-merged DEFAULT_SEARCH_OPTIONS into the request body, so a partial
sortBy dropped the sibling default. With { sortBy: { order: 'desc' } } the request
omitted `column`, which the Storage server requires, returning a 400. Deep-merge
sortBy so a partial override keeps the other default (column or order), matching
how the top-level limit/offset defaults already behave.
@i-anubhav-anand
i-anubhav-anand force-pushed the fix/storage-list-sortby-defaults branch from cc0833a to bf8a2da Compare June 17, 2026 15:04
@i-anubhav-anand

Copy link
Copy Markdown
Contributor Author

Thanks for the careful review, @mandarini — and great catch on the server-side default. You're right that the order-only case ({ sortBy: { order: 'desc' } }) is the one that actually 400s on master, since column gets dropped and the server requires it. I've:

  • Added a test for the order-only case — it fails on master (sends sortBy without column) and passes with the deep merge,
  • Led the description with that scenario, and
  • Rebased on master to fix CI.

Thanks again for taking the time! 🙏

@i-anubhav-anand i-anubhav-anand changed the title fix(storage): keep default sortBy.order when list() overrides only the column fix(storage): keep sortBy defaults when list() is given a partial sortBy Jun 17, 2026
@mandarini
mandarini merged commit 42b7bbc into supabase:master Jun 18, 2026
21 checks passed
spydon added a commit to supabase/supabase-flutter that referenced this pull request Jun 29, 2026
…ortBy (#1490)

## What

`storage.from(bucket).list()` rejected a partially-specified `SortBy`
with a 400.

`SortBy`'s constructor had no default for `column`/`order`, so
`SortBy(column: 'updated_at')` left `order` as `null`. Since
`SearchOptions.toMap()` always emits both keys and the storage client
serializes with `json.encode` (which keeps nulls), the request body
sent:

```json
"sortBy": { "column": "updated_at", "order": null }
```

The Storage server requires both `column` and `order`, so it returned a
400.

This mirrors supabase-js
[#2454](supabase/supabase-js#2454) (commit
`42b7bbc`), which deep-merges the partial `sortBy` onto the defaults.

## Fix

Give `SortBy` constructor defaults so any partially-specified `SortBy`
fills in the missing field, which is the idiomatic Dart equivalent of
the JS deep-merge and works wherever `SortBy` is constructed:

```dart
const SortBy({this.column = 'name', this.order = 'asc'});
```

Behavior:

- `SortBy(column: 'updated_at')` → `{column: 'updated_at', order:
'asc'}`
- `SortBy(order: 'desc')` → `{column: 'name', order: 'desc'}`
- `list()` with no options → `{column: 'name', order: 'asc'}`
(unchanged)

No breaking change for callers passing a complete `SortBy`.

## Tests

Added a `list sortBy defaults` group in `client_test.dart` (using the
in-memory `CustomHttpClient` to inspect the request body) covering
partial column, partial order, no options, and complete sortBy.

Closes SDK-1089
mandarini pushed a commit to supabase/ssr that referenced this pull request Jun 30, 2026
This PR updates `@supabase/supabase-js` to v2.110.0.

**Source**: supabase-js-stable-release

---

## Release Notes

## v2.110.0

## 2.110.0 (2026-06-30)

### 🚀 Features

- **repo:** drop Node.js 20 support
([#2482](supabase/supabase-js#2482))

### ❤️ Thank You

- Katerina Skroumpelou @mandarini
## v2.109.0

## 2.109.0 (2026-06-30)

### 🚀 Features

- **auth:** add custom_claims_allowlist to custom providers admin API
([#2473](supabase/supabase-js#2473))
- **realtime:** add postgres_changes filter builder, new operators and
select ([#2463](supabase/supabase-js#2463))
- **storage:** expose purgeCache for buckets and single objects
([#2429](supabase/supabase-js#2429))

### 🩹 Fixes

- **functions:** honor a caller's Content-Type override regardless of
casing ([#2455](supabase/supabase-js#2455))
- **realtime:** pin @supabase/phoenix and browser test CDN deps
([#2457](supabase/supabase-js#2457))
- **realtime:** add replication connection system message option
([#2470](supabase/supabase-js#2470))
- **storage:** keep sortBy defaults when list() is given a partial
sortBy ([#2454](supabase/supabase-js#2454))

### ❤️ Thank You

- Anubhav Anand @i-anubhav-anand
- Cemal Kılıç @cemalkilic
- Claude Opus 4.8 (1M context)
- Filipe Cabaço @filipecabaco
- Katerina Skroumpelou @mandarini
- Lenny
- Rodrigo Mansueli @mansueli
## v2.108.2

## 2.108.2 (2026-06-15)

### 🩹 Fixes

- **auth:** preserve valid session on refresh failure and cooldown
repeat failures
([#2436](supabase/supabase-js#2436))
- **realtime:** clarify httpSend() 404 error and server migration note
([#2444](supabase/supabase-js#2444))
- **release:** pin Deno and bound JSR publish to survive stranded-task
hangs ([#2439](supabase/supabase-js#2439))
- **release:** restore JSR publish flags and enable for beta
([#2440](supabase/supabase-js#2440))

### ❤️ Thank You

- Katerina Skroumpelou @mandarini
## v2.108.1

## 2.108.1 (2026-06-09)

### 🩹 Fixes

- **ci:** forward DOGFOOD_APP_CLIENT_ID to dogfood workflow
([#2434](supabase/supabase-js#2434))
- **postgrest:** then typing
([#2349](supabase/supabase-js#2349))

### ❤️ Thank You

- Katerina Skroumpelou @mandarini
- Vaibhav @7ttp

This PR was created automatically.

Co-authored-by: supabase-workflow-trigger[bot] <266661614+supabase-workflow-trigger[bot]@users.noreply.github.com>
mandarini pushed a commit to supabase/supabase that referenced this pull request Jul 7, 2026
This PR updates @supabase/*-js libraries to version 2.110.1.

**Source**: supabase-js-stable-release

**Changes**:
- Updated @supabase/supabase-js to 2.110.1
- Updated @supabase/auth-js to 2.110.1
- Updated @supabase/realtime-js to 2.110.1
- Updated @supabase/postgest-js to 2.110.1
- Refreshed pnpm-lock.yaml

---

## Release Notes

## v2.110.1

## 2.110.1 (2026-07-07)

### 🩹 Fixes

- **auth:** defer init-time notifications until initializePromise
resolves ([#2498](supabase/supabase-js#2498))
- **realtime:** suppress disconnected status from onHeartbeat consumers
([#2496](supabase/supabase-js#2496))

### ❤️ Thank You

- Katerina Skroumpelou @mandarini
## v2.110.0

## 2.110.0 (2026-06-30)

### 🚀 Features

- **repo:** drop Node.js 20 support
([#2482](supabase/supabase-js#2482))

### ❤️ Thank You

- Katerina Skroumpelou @mandarini
## v2.109.0

## 2.109.0 (2026-06-30)

### 🚀 Features

- **auth:** add custom_claims_allowlist to custom providers admin API
([#2473](supabase/supabase-js#2473))
- **realtime:** add postgres_changes filter builder, new operators and
select ([#2463](supabase/supabase-js#2463))
- **storage:** expose purgeCache for buckets and single objects
([#2429](supabase/supabase-js#2429))

### 🩹 Fixes

- **functions:** honor a caller's Content-Type override regardless of
casing ([#2455](supabase/supabase-js#2455))
- **realtime:** pin @supabase/phoenix and browser test CDN deps
([#2457](supabase/supabase-js#2457))
- **realtime:** add replication connection system message option
([#2470](supabase/supabase-js#2470))
- **storage:** keep sortBy defaults when list() is given a partial
sortBy ([#2454](supabase/supabase-js#2454))

### ❤️ Thank You

- Anubhav Anand @i-anubhav-anand
- Cemal Kılıç @cemalkilic
- Claude Opus 4.8 (1M context)
- Filipe Cabaço @filipecabaco
- Katerina Skroumpelou @mandarini
- Lenny
- Rodrigo Mansueli @mansueli

This PR was created automatically.

Co-authored-by: supabase-workflow-trigger[bot] <266661614+supabase-workflow-trigger[bot]@users.noreply.github.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.

2 participants