immich-app/immich · error · Error

errors.unable_to_upload_file

Error message

errors.unable_to_upload_file

What it means

In the web app's file uploader, after POSTing the asset to /assets, any HTTP status other than 200/201 throws a localized error ('errors.unable_to_upload_file'). This signals the server rejected the upload (validation failure, auth issue, duplicate handling error, etc.) for that specific device asset.

Solutions

  1. Check the response status/body (network tab) to identify the underlying rejection (401 vs 413 vs 500).
  2. For 413: raise the reverse proxy body-size limit (e.g. nginx client_max_body_size) above your largest photo/video.
  3. For 401: re-authenticate (log in again) and retry the upload.
  4. For 500: check immich-server logs (disk full? database error?) and free space or fix storage config.
  5. Retry failed assets from the upload queue after fixing the cause.

Example fix

// nginx before
client_max_body_size 1m;
// after
client_max_body_size 50000M;
Defensive patterns

Strategy: try-catch

Validate before calling

if (!navigator.onLine) throw new OfflineError('Defer upload until online');
if (file.size === 0) throw new ValidationError('Refusing to upload empty file');

Type guard

const isUploadFailure = (e: unknown): boolean =>
  e instanceof Error && e.message === $t('errors.unable_to_upload_file');

Try / catch

try { await fileUploader(file); } catch (e) { if (isUploadFailure(e)) { uploadAssetsStore.markFailed(deviceAssetId); showToast(t('errors.unable_to_upload_file')); } else throw e; }

Prevention

When it happens

Trigger: fileUploader receives an axios response from the /assets endpoint with a status outside {200, 201} — e.g. 401 (expired session), 413 (payload too large via reverse proxy), 4xx/5xx server errors.

Common situations: Nginx/proxy client_max_body_size smaller than the photo; session expiring mid-upload; server storage full or returning 500; uploading zero-byte or corrupted files the API rejects.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/50b994ac3ad84c35. Report an issue: GitHub.

Appendix: source

Thrown at web/src/lib/utils/file-uploader.ts:210

          };
        }
      } catch (error) {
        console.error(`Error calculating sha1 file=${assetFile.name})`, error);
      }
    }

    if (!responseData) {
      const queryParams = asQueryString(authManager.params);

      uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_uploading') });
      const response = await uploadRequest<AssetMediaResponseDto>({
        url: getBaseUrl() + '/assets' + (queryParams ? `?${queryParams}` : ''),
        data: formData,
        onUploadProgress: (event) => uploadAssetsStore.updateProgress(deviceAssetId, event.loaded, event.total),
      });

      if (![200, 201].includes(response.status)) {
        throw new Error($t('errors.unable_to_upload_file'));
      }

      responseData = response.data;
    }

    if (responseData.status === AssetMediaStatus.Duplicate) {
      uploadAssetsStore.track('duplicate');
    } else {
      uploadAssetsStore.track('success');
    }

    if (albumId && !authManager.isSharedLink) {
      uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_adding_to_album') });
      await addAssetsToAlbums([albumId], [responseData.id], { notify: false });
      uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_added_to_album') });
    }

    uploadAssetsStore.updateItem(deviceAssetId, {

View on GitHub (pinned to e55ac299a4)