transloadit/uppy · critical · Error

[s3mini] Missing ETag in uploadPart response headers

Error message

[s3mini] Missing ETag in uploadPart response headers

What it means

uploadPart relies on the ETag response header to identify the uploaded part for the final CompleteMultipartUpload call. After the PUT succeeds, s3mini reads and sanitizes the `etag` header; if it's absent or unusable (null), it throws this Error because completing the multipart upload would be impossible without the part ETag.

Source

Thrown at packages/@uppy/aws-s3/src/s3-client/S3mini.ts:270

    signal,
  }: IT.UploadPartParams) {
    this._validateUploadPartParams(key, uploadId, partNumber)

    const { xhr } = await this.request({
      request: {
        method: 'PUT',
        key,
        uploadId,
        partNumber,
      },
      data,
      onProgress,
      signal,
    })

    const etag = U.sanitizeETag(xhr.getResponseHeader('etag'))
    if (etag == null) {
      throw new Error(
        `${C.ERROR_PREFIX}Missing ETag in uploadPart response headers`,
      )
    }

    return { etag }
  }

  /**
   * Core XHR upload implementation using @uppy/core/utils fetcher.
   *
   * Features:
   * - Automatic retry with exponential backoff (3 attempts)
   * - Offline detection with automatic resume on reconnect
   * - Stall detection via ProgressTimeout
   */
  private async request({
    request,
    data,

View on GitHub (pinned to 5d4dedd02a)

Solutions

  1. Add `Access-Control-Expose-Headers: ETag` (and typically `ETag` in allowed headers) to the S3 bucket CORS configuration or proxy response headers.
  2. If a reverse proxy fronts S3/MinIO, ensure it forwards the ETag response header and doesn't strip it.
  3. Verify with browser devtools that the PUT part response actually includes an ETag header; if not, fix the server side.

Example fix

// before (bucket CORS missing expose headers)
[{ "AllowedOrigins": ["*"], "AllowedMethods": ["PUT","POST"], "AllowedHeaders": ["*"] }]
// after
[{ "AllowedOrigins": ["*"], "AllowedMethods": ["PUT","POST"], "AllowedHeaders": ["*"], "ExposeHeaders": ["ETag"] }]
Defensive patterns

Strategy: fallback

Try / catch

try { return await s3.uploadPart({ key, uploadId, partNumber, body }) } catch (e) { if (/Missing ETag/.test(String(e?.message))) { console.error('ETag stripped by CORS/proxy — add ExposeHeaders: ETag'); throw e } throw e }

Prevention

When it happens

Trigger: An S3-compatible backend or proxy that strips/renames the ETag header; missing `Access-Control-Expose-Headers: ETag` CORS header so the browser hides it from JS; gateway (nginx/CloudFront) dropping the header; a non-S3 endpoint returning 200 without ETag.

Common situations: Browser uploads where the S3/proxy CORS config doesn't expose ETag — by far the most common cause; self-hosted MinIO/garage behind a reverse proxy; custom middleware that filters response headers.

Related errors


AI-assisted analysis of transloadit/uppy@5d4dedd02a (2026-08-28). Data as JSON: /api/errors/bf985f4efdf4b1d2. Report an issue: GitHub.