outline/outline · error · Error
Upload failed
Error message
Upload failed
What it means
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.
Source
Thrown at app/utils/files.ts:140
// Do not send credentials if uploading to a different origin, as the
// combination of CORS and cookies will cause preflight request failure.
// However S3-like storage on the same host can work with credentials.
if (data.uploadUrl.startsWith("/")) {
xhr.withCredentials = true;
} else {
const parsed = new URL(data.uploadUrl);
const requiresPreflightRequest =
parsed.origin !== window.location.origin;
xhr.withCredentials = !requiresPreflightRequest;
}
xhr.open("POST", data.uploadUrl, true);
xhr.send(formData);
}
});
if (!success) {
throw new Error("Upload failed");
}
return attachment;
};
export { dataUrlToBlob };
const CHAR_FORWARD_SLASH = 47; /* / */
const CHAR_DOT = 46; /* . */
// Based on the NodeJS Library https://github.com/nodejs/node/blob/896b75a4da58a7283d551c4595e0aa454baca3e0/lib/path.js
// Copyright Joyent, Inc. and other Node contributors.
/**
* Returns the extension of the path, from the last occurrence of the "."
* character to the end of the string in the last portion of the path.
*
* @param path the path to evaluate.
* @returns the extension including the leading ".", or an empty string.View on GitHub (pinned to 935a44d4c0)
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)
Example fix
// before
if (!success) {
throw new Error('Upload failed');
}
// after - include status for diagnosis
if (!success) {
throw new Error(`Upload failed (status ${xhr.status || 'none'})`);
} Defensive patterns
Strategy: retry
Type guard
const isUploadFailure = (e: unknown): boolean => e instanceof Error && e.message === 'Upload failed';
Try / catch
try {
return await uploadFile(file, opts);
} catch (e) {
if (e.message !== 'Upload failed') throw e;
Logger.warn('Retrying upload after failure', { name: file.name });
return await uploadFile(file, opts);
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of outline/outline@935a44d4c0 (2026-08-12).
Data as JSON: /api/errors/fb8a2b2fbd494658.
Report an issue: GitHub.