{"record":{"id":"5fab8d0ab5468642","repo":"transloadit/uppy","slug":"s3-the-part-numbers-must-be-a-number-between-1-an","errorCode":null,"errorMessage":"s3: the part numbers must be a number between 1 and 10000.","messagePattern":"s3: the part numbers must be a number between 1 and 10000\\.","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"packages/@uppy/companion/src/server/controllers/s3.ts","lineNumber":409,"sourceCode":"    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\n    if (typeof partNumbers !== 'string') {\n      res.status(400).json({\n        error:\n          's3: the part numbers must be passed as a comma separated query parameter. For example: \"?partNumbers=4,6,7,21\"',\n      })\n      return\n    }\n\n    const partNumbersArray = partNumbers.split(',')\n    if (!partNumbersArray.every((partNumber) => parseInt(partNumber, 10))) {\n      res.status(400).json({\n        error: 's3: the part numbers must be a number between 1 and 10000.',\n      })\n      return\n    }\n\n    const bucket = getBucket({ bucketOrFn: config.bucket, req })\n\n    Promise.all(\n      partNumbersArray.map((partNumber) => {\n        return getSignedUrl(\n          client,\n          new UploadPartCommand({\n            Bucket: bucket,\n            Key: key,\n            UploadId: uploadId,\n            PartNumber: Number(partNumber),\n            Body: '',\n          }),","sourceCodeStart":391,"sourceCodeEnd":427,"githubUrl":"https://github.com/transloadit/uppy/blob/5d4dedd02a1ac0ae022c75c54aca76558f88e256/packages/@uppy/companion/src/server/controllers/s3.ts#L391-L427","documentation":"After parsing the comma-separated partNumbers query parameter, Companion validates every entry parses as an integer via parseInt. Any non-numeric token ('abc', '', 'null') fails and results in HTTP 400. AWS requires part numbers between 1 and 10000, which is the constraint the message refers to.","triggerScenarios":"Sending ?partNumbers=1,2,foo or ?partNumbers=1,,2 (empty token), or interpolating undefined/null values into the joined string, e.g. [1, undefined, 3].join(',') producing '1,,3'.","commonSituations":"Building the part list from sparse arrays or filtered lists without removing holes; 0-based part indexes ('0' parses to 0 which is falsy and fails); string interpolation of unvalidated user input.","solutions":["Filter and validate before joining: parts.filter(Number.isInteger).join(',') with parts in the 1..10000 range.","Check for 0-based indexing bugs — part numbers must start at 1.","Log the outgoing query string to spot empty tokens like '1,,2' from sparse arrays."],"exampleFix":"// before\nconst partNumbers = uploadedParts.map((p) => p.index).join(',') // may contain 0 or gaps\n\n// after\nconst partNumbers = uploadedParts\n  .map((p) => p.index + 1)\n  .filter((n) => Number.isInteger(n) && n >= 1 && n <= 10000)\n  .join(',')","handlingStrategy":"type-guard","validationCode":"const validParts = parts.filter(\n  (n): n is number => Number.isInteger(n) && n >= 1 && n <= 10000,\n)\nconst partNumbers = validParts.join(',')","typeGuard":"const allValidParts = (arr: unknown[]): boolean =>\n  arr.length > 0 &&\n  arr.every((n) => Number.isInteger(n) && n >= 1 && n <= 10000)","tryCatchPattern":null,"preventionTips":["Filter sparse arrays and non-integers before joining","Guard against 0-based part indexes","Clamp total parts to 10000 by raising chunk size"],"tags":["s3","companion","multipart-upload","batch","part-number","request-validation"],"backgroundTag":"parameter-out-of-range","analyzedSha":"5d4dedd02a1ac0ae022c75c54aca76558f88e256","analyzedAt":"2026-08-28T12:18:41.267Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}