{"record":{"id":"0003e2cde22c62c6","repo":"transloadit/uppy","slug":"s3-filename-returned-from-getkey-must-be-a-stri","errorCode":null,"errorMessage":"s3: filename returned from `getKey` must be a string","messagePattern":"s3: filename returned from `getKey` must be a string","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"packages/@uppy/companion/src/server/controllers/s3.ts","lineNumber":213,"sourceCode":"      return\n    }\n\n    const truncatedFilename = truncateFilename(\n      filename,\n      req.companion.options.maxFilenameLength,\n    )\n\n    const bucket = getBucket({\n      bucketOrFn: config.bucket,\n      req,\n      filename: truncatedFilename,\n      metadata,\n    })\n\n    const key = config.getKey({ req, filename: truncatedFilename, metadata })\n\n    if (typeof key !== 'string') {\n      res.status(500).json({\n        error: 's3: filename returned from `getKey` must be a string',\n      })\n      return\n    }\n    if (typeof type !== 'string') {\n      res.status(400).json({ error: 's3: content type must be a string' })\n      return\n    }\n\n    const params = {\n      Bucket: bucket,\n      Key: key,\n      ContentType: type,\n      Metadata: rfc2047EncodeMetadata(metadata),\n      ...(config.acl != null && { ACL: config.acl }),\n      ...(config.awsSse != null && { ServerSideEncryption: config.awsSse }),\n      ...(config.awsSseKmsKeyId != null && {\n        SSEKMSKeyId: config.awsSseKmsKeyId,","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/transloadit/uppy/blob/5d4dedd02a1ac0ae022c75c54aca76558f88e256/packages/@uppy/companion/src/server/controllers/s3.ts#L195-L231","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Fix your getKey function so every code path returns a string, e.g. return \\`\\${userId}/\\${filename}\\` with a fallback like filename || 'upload'.","Log the arguments (req, filename, metadata) inside getKey to see which input is missing and causes an undefined return.","If getKey is async, make sure Companion's config supports it and that you return (not just resolve) the string value.","As a last resort remove getKey to use Companion's default key generation."],"exampleFix":"// before\ngetKey: ({ req, filename }) => {\n  if (req.user) {\n    return `${req.user.id}/${filename}`\n  }\n  // falls through, returns undefined -> 500\n}\n\n// after\ngetKey: ({ req, filename }) =>\n  `${req.user?.id ?? 'anonymous'}/${filename}`","handlingStrategy":"type-guard","validationCode":"const results = []\nfor (const { req, filename, metadata } of cases) {\n  const key = config.getKey({ req, filename, metadata })\n  if (typeof key !== 'string') throw new Error(`getKey returned ${key} for ${filename}`)\n}","typeGuard":"const isStringKey = (v: unknown): v is string => typeof v === 'string' && v.length > 0\n// wrap your getKey:\nconst safeGetKey = (opts) => {\n  const k = getKey(opts)\n  return isStringKey(k) ? k : `fallback/${opts.filename}`\n}","tryCatchPattern":null,"preventionTips":["Always give getKey a default branch that returns a string","Unit-test getKey with missing metadata/user to confirm it still returns a string","Never let getKey implicitly return undefined (add explicit returns on all paths)"],"tags":["s3","companion","multipart-upload","configuration","type-validation"],"backgroundTag":"custom-callback-invalid-return-value","analyzedSha":"5d4dedd02a1ac0ae022c75c54aca76558f88e256","analyzedAt":"2026-08-28T12:18:41.267Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}