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

  1. 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.
  2. Upgrade @uppy/aws-s3 and @uppy/companion to matching versions so the request contract matches.
  3. 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

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


AI-assisted analysis of transloadit/uppy@5d4dedd02a (2026-08-28). Data as JSON: /api/errors/2beeefc564766934. Report an issue: GitHub.