transloadit/uppy · error

s3: filename returned from `getKey` must be a string

Error message

s3: filename returned from `getKey` must be a string

What it means

Companion's S3 multipart upload initiation calls the user-configured getKey(options) function to derive the S3 object key from the request. If getKey returns anything other than a string (undefined, null, number, object), Companion responds with HTTP 500 and this message. It exists to prevent sending an invalid Key to AWS S3's CreateMultipartUpload, which would fail opaquely otherwise.

Source

Thrown at packages/@uppy/companion/src/server/controllers/s3.ts:213

      return
    }

    const truncatedFilename = truncateFilename(
      filename,
      req.companion.options.maxFilenameLength,
    )

    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,

View on GitHub (pinned to 5d4dedd02a)

Solutions

  1. Fix your getKey function so every code path returns a string, e.g. return \`\${userId}/\${filename}\` with a fallback like filename || 'upload'.
  2. Log the arguments (req, filename, metadata) inside getKey to see which input is missing and causes an undefined return.
  3. If getKey is async, make sure Companion's config supports it and that you return (not just resolve) the string value.
  4. As a last resort remove getKey to use Companion's default key generation.

Example fix

// before
getKey: ({ req, filename }) => {
  if (req.user) {
    return `${req.user.id}/${filename}`
  }
  // falls through, returns undefined -> 500
}

// after
getKey: ({ req, filename }) =>
  `${req.user?.id ?? 'anonymous'}/${filename}`
Defensive patterns

Strategy: type-guard

Validate before calling

const results = []
for (const { req, filename, metadata } of cases) {
  const key = config.getKey({ req, filename, metadata })
  if (typeof key !== 'string') throw new Error(`getKey returned ${key} for ${filename}`)
}

Type guard

const isStringKey = (v: unknown): v is string => typeof v === 'string' && v.length > 0
// wrap your getKey:
const safeGetKey = (opts) => {
  const k = getKey(opts)
  return isStringKey(k) ? k : `fallback/${opts.filename}`
}

Prevention

When it happens

Trigger: POST to /s3/multipart with a body whose getKey implementation returns a non-string, e.g. an arrow function without a return statement (returns undefined), a function returning a number, or a getKey that conditionally returns undefined for some inputs (e.g. missing metadata field).

Common situations: Writing a custom getKey in companion options that forgets a return path, refactoring getKey to async without awaiting, returning a template that evaluates to undefined because a variable is missing, or copying an example getKey that assumes fields not present in the upload metadata.

Related errors


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