Bug report
Describe the bug
When uploading text files to Supabase Storage using the Python SDK with a
Content-Type that includes a charset parameter (for example
text/markdown; charset=UTF-8), the charset information is silently dropped
during the upload process.
Although the upload request explicitly includes the full Content-Type
(including charset), after the upload completes:
- The file metadata returned by the Storage
info / list API
- The HTTP response headers returned by public URLs or signed URLs
only contain the base MIME type (e.g. text/markdown) and never include the
charset parameter.
This leads to incorrect or ambiguous character encoding handling in browsers and
HTTP clients that rely on response headers for charset detection.
To Reproduce
1. Upload a file with charset specified
from supabase import create_client
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
with open("example.md", "rb") as f:
supabase.storage.from_("test-bucket").upload(
"example.md",
f,
file_options={
"content-type": "text/markdown; charset=UTF-8"
}
)
2. Verify upload request
- The actual HTTP request sent by the Python SDK is
multipart/form-data
- The file part includes:
Content-Type: text/markdown; charset=UTF-8
3. Query file metadata
files = supabase.storage.from_("test-bucket").list()
Returned metadata shows:
contentType: "text/markdown"
❌ charset=UTF-8 is missing.
4. Access via signed URL or public URL
url = supabase.storage.from_("test-bucket").create_signed_url(
"example.md", 60
)["signedURL"]
Response header:
Content-Type: text/markdown
❌ charset=UTF-8 is missing.
Expected behavior
If a file is uploaded with:
Content-Type: text/markdown; charset=UTF-8
Supabase Storage should either:
- Preserve the full
Content-Type (including charset) in stored metadata and
HTTP responses
or
- Clearly document that MIME parameters (such as
charset) are intentionally
stripped and unsupported
Root cause analysis (from backend code inspection)
After inspecting the Supabase Storage backend source code, the root cause is in
src/storage/backend/s3/adapter.ts, specifically the uploadObject method:
The Problem
-
Content-Type is correctly extracted: During multipart uploads, the backend
correctly extracts the full Content-Type (including charset) from
formData.fields.contentType?.value in fileUploadFromRequest
(src/storage/uploader.ts:344).
-
Content-Type is correctly sent to S3: The full Content-Type (including
charset) is passed to S3's Upload command as the ContentType parameter
(src/storage/backend/s3/adapter.ts:152).
-
Charset is lost when retrieving metadata: After upload completes, for
non-zero-byte files, the code calls headObject to retrieve metadata
(src/storage/backend/s3/adapter.ts:175). S3's HeadObjectCommand returns a
ContentType that has been normalized/stripped of the charset parameter.
-
Normalized value overwrites original: The code uses the mimetype from
headObject response (src/storage/backend/s3/adapter.ts:190) instead of the
original contentType parameter, causing the charset to be lost.
Code Evidence
// src/storage/backend/s3/adapter.ts:174-190
const metadata = hasUploadedBytes
? await this.headObject(bucketName, key, version)
: {
mimetype: contentType,
// ...
}
return {
// ...
mimetype: metadata.mimetype,
// ...
}
Why Zero-Byte Files Work
For zero-byte files, the code bypasses headObject and directly uses the
original contentType parameter (line 179), which is why charset is
preserved for empty files but not for files with content.
The Fix
The code should use the original contentType parameter for non-zero-byte
files instead of relying on headObject's normalized response, or merge the
original contentType with other metadata from headObject.
Why this is a bug (not a feature request)
charset is a standard, semantically significant part of Content-Type
- Browsers and HTTP clients rely on it for correct text decoding
- The Python SDK explicitly sends the full
Content-Type
- Supabase Storage silently drops it without documentation or warning
System information
- OS: macOS / Linux
- SDK: supabase==2.27.0, storage3==2.27.0
- Python version: 3.12
- Storage: Supabase managed Storage
Bug report
Describe the bug
When uploading text files to Supabase Storage using the Python SDK with a
Content-Typethat includes acharsetparameter (for exampletext/markdown; charset=UTF-8), thecharsetinformation is silently droppedduring the upload process.
Although the upload request explicitly includes the full
Content-Type(including
charset), after the upload completes:info/listAPIonly contain the base MIME type (e.g.
text/markdown) and never include thecharsetparameter.This leads to incorrect or ambiguous character encoding handling in browsers and
HTTP clients that rely on response headers for charset detection.
To Reproduce
1. Upload a file with charset specified
2. Verify upload request
multipart/form-data3. Query file metadata
Returned metadata shows:
❌
charset=UTF-8is missing.4. Access via signed URL or public URL
curl -I "<signed-url>"Response header:
❌
charset=UTF-8is missing.Expected behavior
If a file is uploaded with:
Supabase Storage should either:
Content-Type(includingcharset) in stored metadata andHTTP responses
or
charset) are intentionallystripped and unsupported
Root cause analysis (from backend code inspection)
After inspecting the Supabase Storage backend source code, the root cause is in
src/storage/backend/s3/adapter.ts, specifically theuploadObjectmethod:The Problem
Content-Type is correctly extracted: During multipart uploads, the backend
correctly extracts the full
Content-Type(includingcharset) fromformData.fields.contentType?.valueinfileUploadFromRequest(
src/storage/uploader.ts:344).Content-Type is correctly sent to S3: The full
Content-Type(includingcharset) is passed to S3'sUploadcommand as theContentTypeparameter(
src/storage/backend/s3/adapter.ts:152).Charset is lost when retrieving metadata: After upload completes, for
non-zero-byte files, the code calls
headObjectto retrieve metadata(
src/storage/backend/s3/adapter.ts:175). S3'sHeadObjectCommandreturns aContentTypethat has been normalized/stripped of thecharsetparameter.Normalized value overwrites original: The code uses the
mimetypefromheadObjectresponse (src/storage/backend/s3/adapter.ts:190) instead of theoriginal
contentTypeparameter, causing thecharsetto be lost.Code Evidence
Why Zero-Byte Files Work
For zero-byte files, the code bypasses
headObjectand directly uses theoriginal
contentTypeparameter (line 179), which is whycharsetispreserved for empty files but not for files with content.
The Fix
The code should use the original
contentTypeparameter for non-zero-bytefiles instead of relying on
headObject's normalized response, or merge theoriginal
contentTypewith other metadata fromheadObject.Why this is a bug (not a feature request)
charsetis a standard, semantically significant part ofContent-TypeContent-TypeSystem information