{"record":{"id":"2d2bab3df53f71d8","repo":"transloadit/uppy","slug":"s3-uploadid-must-be-provided","errorCode":null,"errorMessage":"s3: uploadId must be provided.","messagePattern":"s3: uploadId must be provided\\.","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"packages/@uppy/companion/src/server/controllers/s3.ts","lineNumber":327,"sourceCode":"   *\n   * Expected URL parameters:\n   *  - uploadId - The uploadId returned from `createMultipartUpload`.\n   *  - partNumber - This part's index in the file (1-10000).\n   * Expected query parameters:\n   *  - key - The object key in the S3 bucket.\n   * Response JSON:\n   *  - url - The URL to upload to, including signed query parameters.\n   */\n  function signPartUpload(req: Request, res: Response, next: NextFunction) {\n    const client = getS3Client(req, res)\n    if (!client) return\n\n    const uploadId = req.params['uploadId']\n    const partNumber = req.params['partNumber']\n    const key = req.query['key']\n\n    if (typeof uploadId !== 'string' || uploadId.length === 0) {\n      res.status(400).json({ error: 's3: uploadId must be provided.' })\n      return\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    if (typeof partNumber !== 'string' || !parseInt(partNumber, 10)) {\n      res.status(400).json({\n        error: 's3: the part number must be a number between 1 and 10000.',\n      })\n      return\n    }\n\n    const bucket = getBucket({ bucketOrFn: config.bucket, req })\n","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/transloadit/uppy/blob/5d4dedd02a1ac0ae022c75c54aca76558f88e256/packages/@uppy/companion/src/server/controllers/s3.ts#L309-L345","documentation":"The part-upload signing endpoint POST /s3/multipart/:uploadId/:partNumber requires the route parameter uploadId to be a non-empty string. It is the AWS multipart upload ID returned when the multipart upload was created. If it's missing or empty, Companion returns HTTP 400 with this message.","triggerScenarios":"Calling the sign-part endpoint with an empty uploadId path segment (POST /s3/multipart//5), or with undefined/null interpolated into the URL by a client that lost track of the uploadId after creating the multipart upload.","commonSituations":"Client state lost between creating the multipart upload and uploading parts (page reload, component remount), or URL construction bugs where uploadId is undefined due to a typo or async race.","solutions":["Store the uploadId returned from the create-multipart response and include it in the URL: POST /s3/multipart/${uploadId}/${partNumber}?key=...","Guard client-side: only start part uploads once uploadId is a non-empty string.","Re-create the multipart upload (via POST /s3/multipart) if the uploadId was lost, and abort the stale one if known."],"exampleFix":"// before\nconst url = `/s3/multipart/${upload?.id}/${partNumber}` // may be /s3/multipart/undefined/1\n\n// after\nif (!upload?.id) throw new Error('missing uploadId')\nconst url = `/s3/multipart/${upload.id}/${partNumber}`","handlingStrategy":"validation","validationCode":"if (typeof uploadId !== 'string' || uploadId.length === 0) {\n  throw new Error('Cannot sign part: multipart upload was not created yet')\n}\nconst url = `/s3/multipart/${encodeURIComponent(uploadId)}/${partNumber}`","typeGuard":"const hasUploadId = (u: unknown): u is string =>\n  typeof u === 'string' && u.length > 0","tryCatchPattern":"try {\n  const res = await signPart(...)\n  if (res.status === 400) handleBadRequest(await res.json())\n} catch (e) {\n  // network-level errors only; 400s are handled above\n}","preventionTips":["Create the multipart upload first and await its uploadId before scheduling parts","Treat a missing uploadId as 'restart upload', not as a retryable error","Store uploadId in durable state when uploads span page reloads"],"tags":["s3","companion","multipart-upload","path-parameters","request-validation"],"backgroundTag":"missing-required-request-field","analyzedSha":"5d4dedd02a1ac0ae022c75c54aca76558f88e256","analyzedAt":"2026-08-28T12:18:41.267Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}