Skip to content

fix(functions): honor a caller's Content-Type override regardless of casing - #2455

Merged
mandarini merged 1 commit into
supabase:masterfrom
i-anubhav-anand:fix/functions-content-type-case
Jun 18, 2026
Merged

fix(functions): honor a caller's Content-Type override regardless of casing#2455
mandarini merged 1 commit into
supabase:masterfrom
i-anubhav-anand:fix/functions-content-type-case

Conversation

@i-anubhav-anand

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

Copy link
Copy Markdown
Contributor

🔍 Description

What changed?

FunctionsClient.invoke() decided whether to auto-set a Content-Type using a case-sensitive hasOwnProperty('Content-Type') check. HTTP header names are case-insensitive, so a caller-supplied lowercase content-type was not detected. This matches the header name case-insensitively before deciding to auto-set/serialize.

Why was this change needed?

With a lowercase content-type, the SDK fell into the auto-detect branch: it set its own Content-Type: application/json, JSON-stringified the body, then merged the caller's header on top — producing two conflicting Content-Type headers and ignoring the caller's intent. The docs state you can override by passing your own Content-Type header.

📸 Examples / Proof

Tests live in test/spec/params.spec.ts inside the existing body stringify with custom headers block (reusing its customFetch harness). On master the lowercase case fails:

- Expected:  ["application/vnd.api+json"]
+ Received:  ["application/json", "application/vnd.api+json"]

The lowercase test also asserts the request body is still '{"a":1}', locking in that the fix routes through the correct (JSON) serialization branch. The uppercase baseline is unchanged.

🔄 Breaking changes

  • This PR contains no breaking changes

📋 Checklist

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

📝 Additional notes

Rebased on master. Addressed review: moved the tests into params.spec.ts (removed the standalone file) and added the request-body assertion for the lowercase case.

@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@2455

@supabase/functions-js

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

@supabase/postgrest-js

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

@supabase/realtime-js

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

@supabase/storage-js

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

@supabase/supabase-js

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

commit: 8e94d7a

@mandarini

Copy link
Copy Markdown
Contributor

Hi @i-anubhav-anand, thank you so much for contributing to Supabase! 💚 This is a really nice catch, thanks for this PR!

I have just two small nitpicks, both optional and neither blocking:

  1. The new test could live in the existing test/spec/params.spec.ts file, inside the body stringify with custom headers describe block. It already has the same custom fetch setup pattern, so adding your two cases there would keep all the Content-Type behavior in one place and avoid duplicating the client and capture harness. Totally your call.

  2. The test asserts on the resulting Content-Type header, which is the important part. Would you be open to also asserting on the request body for the lowercase case? The fix changes which branch serializes the body, so a quick check that the body is still the expected JSON would lock in both halves of the behavior.

Neither of these changes what the PR does, and the fix itself looks good to me.

Thank you again for taking the time to track this down and write it up so clearly. It is contributions like yours that keep these libraries solid.

@mandarini

Copy link
Copy Markdown
Contributor

Also, please @i-anubhav-anand rebase with master to fix CI!

…casing

invoke() detected a caller-supplied Content-Type with a case-sensitive
hasOwnProperty('Content-Type') check, so a lowercase 'content-type' header
slipped through: the SDK auto-set its own Content-Type and JSON-stringified the
body, emitting a second, conflicting Content-Type header. Match the header name
case-insensitively, since HTTP header names are case-insensitive.
@i-anubhav-anand
i-anubhav-anand force-pushed the fix/functions-content-type-case branch from f96164e to 8e94d7a Compare June 17, 2026 15:16
@i-anubhav-anand

Copy link
Copy Markdown
Contributor Author

Thanks @mandarini! 💚 Addressed both nitpicks:

  1. Moved the two tests into test/spec/params.spec.ts inside the body stringify with custom headers block, reusing its existing customFetch harness (and removed the standalone file).
  2. The lowercase test now also asserts the request body is '{"a":1}', locking in that the fix routes through the JSON serialization branch.

Also rebased on master to fix CI. Appreciate the kind words and the thorough review!

@mandarini
mandarini merged commit 0564308 into supabase:master Jun 18, 2026
21 checks passed
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