transloadit/uppy · error
s3: content type must be a string
Error message
s3: content type must be a string
What it means
When initiating an S3 multipart upload, Companion requires the uploaded file's MIME type to be a string before passing it as ContentType to S3's CreateMultipartUpload. If the type field in the request body is missing or not a string, it rejects with HTTP 400. This prevents creating S3 objects with an invalid content type.
Source
Thrown at packages/@uppy/companion/src/server/controllers/s3.ts:219
)
const bucket = getBucket({
bucketOrFn: config.bucket,
req,
filename: truncatedFilename,
metadata,
})
const key = config.getKey({ req, filename: truncatedFilename, metadata })
if (typeof key !== 'string') {
res.status(500).json({
error: 's3: filename returned from `getKey` must be a string',
})
return
}
if (typeof type !== 'string') {
res.status(400).json({ error: 's3: content type must be a string' })
return
}
const params = {
Bucket: bucket,
Key: key,
ContentType: type,
Metadata: rfc2047EncodeMetadata(metadata),
...(config.acl != null && { ACL: config.acl }),
...(config.awsSse != null && { ServerSideEncryption: config.awsSse }),
...(config.awsSseKmsKeyId != null && {
SSEKMSKeyId: config.awsSseKmsKeyId,
}),
}
client.send(new CreateMultipartUploadCommand(params)).then((data) => {
res.json({
key: data.Key,View on GitHub (pinned to 5d4dedd02a)
Solutions
- Ensure the client sends the file's MIME type in the request body, e.g. { filename, type: file.type } — use file.type || 'application/octet-stream' as a fallback since some files have an empty type.
- Upgrade @uppy/aws-s3 and @uppy/companion to matching versions so the request contract matches.
- If you're calling the endpoint manually, include a string type in the JSON body.
Example fix
// before (client)
await companionClient.post('/s3/multipart', { filename: file.name })
// after
await companionClient.post('/s3/multipart', {
filename: file.name,
type: file.type || 'application/octet-stream',
}) Defensive patterns
Strategy: validation
Validate before calling
const type = file.type && typeof file.type === 'string' ? file.type : 'application/octet-stream'
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filename: file.name, type, metadata }),
}) Type guard
const isNonEmptyString = (v: unknown): v is string => typeof v === 'string' && v.length > 0
Try / catch
if (!res.ok) {
const { error } = await res.json()
if (error.includes('content type must be a string')) {
// retry once with type: 'application/octet-stream'
}
} Prevention
- Always default file.type to application/octet-stream — browsers leave it empty for unknown extensions
- Send type in every multipart-create request body
- Keep @uppy/aws-s3 and companion versions in sync
When it happens
Trigger: POST /s3/multipart with a JSON body that omits the type field, sets it to null, or sends a non-string value; typically caused by an uploader client that doesn't set the file's type before requesting multipart upload credentials.
Common situations: Custom uploader implementations or older @uppy/aws-s3 client versions that don't include the file MIME type in the multipart params; files whose type is an empty string or undefined in the browser (common for files without a known extension).
Related errors
- s3: the object key must be passed as a query parameter. For
- s3: uploadId must be provided.
- s3: the part number must be a number between 1 and 10000.
- s3: the part numbers must be passed as a comma separated que
- s3: the part numbers must be a number between 1 and 10000.
AI-assisted analysis of transloadit/uppy@5d4dedd02a (2026-08-28).
Data as JSON: /api/errors/2beeefc564766934.
Report an issue: GitHub.