{"record":{"id":"fb8a2b2fbd494658","repo":"outline/outline","slug":"upload-failed","errorCode":null,"errorMessage":"Upload failed","messagePattern":"Upload failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/utils/files.ts","lineNumber":140,"sourceCode":"      // Do not send credentials if uploading to a different origin, as the\n      // combination of CORS and cookies will cause preflight request failure.\n      // However S3-like storage on the same host can work with credentials.\n      if (data.uploadUrl.startsWith(\"/\")) {\n        xhr.withCredentials = true;\n      } else {\n        const parsed = new URL(data.uploadUrl);\n        const requiresPreflightRequest =\n          parsed.origin !== window.location.origin;\n        xhr.withCredentials = !requiresPreflightRequest;\n      }\n\n      xhr.open(\"POST\", data.uploadUrl, true);\n      xhr.send(formData);\n    }\n  });\n\n  if (!success) {\n    throw new Error(\"Upload failed\");\n  }\n\n  return attachment;\n};\n\nexport { dataUrlToBlob };\n\nconst CHAR_FORWARD_SLASH = 47; /* / */\nconst CHAR_DOT = 46; /* . */\n\n// Based on the NodeJS Library https://github.com/nodejs/node/blob/896b75a4da58a7283d551c4595e0aa454baca3e0/lib/path.js\n// Copyright Joyent, Inc. and other Node contributors.\n/**\n * Returns the extension of the path, from the last occurrence of the \".\"\n * character to the end of the string in the last portion of the path.\n *\n * @param path the path to evaluate.\n * @returns the extension including the leading \".\", or an empty string.","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/outline/outline/blob/935a44d4c003429645a956141b2c4eb695f34b6e/app/utils/files.ts#L122-L158","documentation":"Thrown by uploadFile (app/utils/files.ts) after the XMLHttpRequest upload resolves with success === false. success is set in the loadend handler to (readyState===4 && 200<=status<400). Any non-2xx/3xx response, an aborted request, or a network-level error before response triggers this generic message. Detailed status is logged via Logger but not exposed.","triggerScenarios":"S3/storage backend returns 4xx/5xx (e.g. presigned URL expired, signature mismatch, bucket policy denial); XHR aborted by user navigation; CORS error on cross-origin upload to S3; storage quota exceeded; preflight fails because withCredentials was set on a cross-origin PUT.","commonSituations":"Clock skew invalidating presigned URLs; misconfigured S3/MinIO/GCS backend; storage credentials rotated but not updated; file larger than backend max; CORS rules on the bucket missing PUT/POST or the Authorization/Content-Type headers.","solutions":["Inspect Logger output for the real xhr.status / xhr.statusText preceding this throw","Verify storage backend credentials, endpoint, and bucket CORS policy allow the upload method and headers","Confirm server clock is in sync (NTP) for presigned URL validity","Check that withCredentials is not set for cross-origin S3 (the code already disables it, but verify bucket-side)"],"exampleFix":"// before\nif (!success) {\n  throw new Error('Upload failed');\n}\n\n// after - include status for diagnosis\nif (!success) {\n  throw new Error(`Upload failed (status ${xhr.status || 'none'})`);\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"const isUploadFailure = (e: unknown): boolean =>\n  e instanceof Error && e.message === 'Upload failed';","tryCatchPattern":"try {\n  return await uploadFile(file, opts);\n} catch (e) {\n  if (e.message !== 'Upload failed') throw e;\n  Logger.warn('Retrying upload after failure', { name: file.name });\n  return await uploadFile(file, opts);\n}","preventionTips":["Log xhr.status/xhr.statusText alongside the throw for diagnosis","Verify storage backend CORS allows PUT/POST and required headers","Sync server clock via NTP to keep presigned URLs valid","Cap upload size below the backend limit before initiating"],"tags":["upload","storage","xhr","attachments"],"backgroundTag":null,"analyzedSha":"935a44d4c003429645a956141b2c4eb695f34b6e","analyzedAt":"2026-08-12T22:40:11.882Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}