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

  1. Inspect Logger output for the real xhr.status / xhr.statusText preceding this throw
  2. Verify storage backend credentials, endpoint, and bucket CORS policy allow the upload method and headers
  3. Confirm server clock is in sync (NTP) for presigned URL validity
  4. 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

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.