{"record":{"id":"3689ca48169d12e7","repo":"transloadit/uppy","slug":"s3-the-object-key-must-be-passed-as-a-query-param","errorCode":null,"errorMessage":"s3: the object key must be passed as a query parameter. For example: \"?key=abc.jpg\"","messagePattern":"s3: the object key must be passed as a query parameter\\. For example: \"\\?key=abc\\.jpg\"","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"packages/@uppy/companion/src/server/controllers/s3.ts","lineNumber":271,"sourceCode":"   *     - PartNumber - the index of this part.\n   *     - ETag - a hash of this part's contents, used to refer to it.\n   *     - Size - size of this part.\n   */\n  function getUploadedParts(req: Request, res: Response, next: NextFunction) {\n    const client = getS3Client(req, res)\n    if (!client) return\n    const s3Client = client\n\n    const { uploadId } = req.params\n    const { key } = req.query\n\n    assert(\n      typeof uploadId === 'string' && uploadId.length > 0,\n      's3: uploadId must be provided.',\n    )\n\n    if (typeof key !== 'string') {\n      res.status(400).json({\n        error:\n          's3: the object key must be passed as a query parameter. For example: \"?key=abc.jpg\"',\n      })\n      return\n    }\n    const keyStr = key\n\n    const bucket = getBucket({ bucketOrFn: config.bucket, req })\n\n    const parts: Part[] = []\n\n    const listPartsPage = (startAt?: string) => {\n      s3Client\n        .send(\n          new ListPartsCommand({\n            Bucket: bucket,\n            Key: keyStr,\n            UploadId: uploadId,","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/transloadit/uppy/blob/5d4dedd02a1ac0ae022c75c54aca76558f88e256/packages/@uppy/companion/src/server/controllers/s3.ts#L253-L289","documentation":"The GET /s3/multipart/:uploadId endpoint (listing already uploaded parts) requires the S3 object key to be passed as the key query parameter so Companion knows which object the uploadId belongs to. Without a string key, Companion returns HTTP 400 with this message including the expected format ?key=abc.jpg.","triggerScenarios":"Calling GET /s3/multipart/<uploadId> without a ?key=... query string, or with key passed in the body or headers instead of the query string, or as an array (?key=a&key=b) which Express parses into a non-string.","commonSituations":"Custom clients that only pass uploadId in the URL and forget the key; refactors that move the key into a header; duplicate query parameters causing Express to yield an array.","solutions":["Append the object key as a query parameter: GET /s3/multipart/${encodeURIComponent(uploadId)}?key=${encodeURIComponent(key)}.","Make sure you only send the key once in the query string (duplicate ?key= values become an array and fail the typeof string check).","Keep the key returned by the create-multipart-upload / getKey step and reuse it verbatim for subsequent part calls."],"exampleFix":"// before\nfetch(`${companionUrl}/s3/multipart/${uploadId}`)\n\n// after\nfetch(\n  `${companionUrl}/s3/multipart/${encodeURIComponent(uploadId)}` +\n  `?key=${encodeURIComponent(key)}`,\n)","handlingStrategy":"validation","validationCode":"const url =\n  `${companion}/s3/multipart/${encodeURIComponent(uploadId)}` +\n  `?key=${encodeURIComponent(key)}`","typeGuard":"function hasKeyParam(key: unknown): key is string {\n  return typeof key === 'string' && key.length > 0\n}","tryCatchPattern":"if (res.status === 400) {\n  const body = await res.json()\n  if (body.error.includes('object key must be passed')) {\n    throw new Error(`Missing ?key= for uploadId ${uploadId}`)\n  }\n}","preventionTips":["Centralize URL building for multipart endpoints in one helper that always appends key","encodeURIComponent the key — encoded values still pass the string check","Persist the key returned at creation and pass it to every subsequent call"],"tags":["s3","companion","multipart-upload","query-parameters","request-validation"],"backgroundTag":"missing-query-parameter","analyzedSha":"5d4dedd02a1ac0ae022c75c54aca76558f88e256","analyzedAt":"2026-08-28T12:18:41.267Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}