Tanstack Start

Errors & validation

S3DeliveryError, the error-code union, and validateFiles.

Every failure in the SDK — a client-side constraint violation, a failed PUT, an aborted upload, a rejected route handler — arrives as a single error type: S3DeliveryError. It carries a stable, machine-readable code you can branch on.

import { S3DeliveryError } from "s3delivery";

try {
  await uploadFiles(files, { maxFileSize: 5 * 1024 * 1024 });
} catch (e) {
  if (e instanceof S3DeliveryError && e.code === "FILE_TOO_LARGE") {
    toast.error("That file is too big (max 5 MB).");
  }
}

S3DeliveryError

class S3DeliveryError extends Error {
  readonly code: S3DeliveryErrorCode; // stable string, e.g. "FILE_TOO_LARGE"
  readonly status?: number; // HTTP status, when from a response
}
  • code is the field to branch on — it is stable across messages and HTTP layers. The browser SDK re-throws errors from your route handler with the same code, so client and server agree.
  • status is present when the error came from an HTTP response (e.g. 413 for FILE_TOO_LARGE from the server).
  • message is human-readable and may change — do not match on it.

The code type is an open string union, so server-defined codes surface verbatim, but the SDK's own codes autocomplete.

Error codes

Client-side validation (before any network request)

CodeThrown when
TOO_MANY_FILESMore files than maxFiles were selected.
FILE_TOO_LARGEA file exceeds maxFileSize.
TYPE_NOT_ALLOWEDA file's MIME type is not in allowedFileTypes.
NO_FILESAn upload was attempted with an empty selection.

Transport / engine

CodeThrown when
UPLOAD_FAILEDA storage PUT returned a non-2xx status.
NETWORK_ERRORThe request failed at the network layer.
ABORTEDThe upload was cancelled via an AbortSignal / control.abort().
NO_ETAGA multipart part response exposed no ETag (usually a CORS config gap).

Client / auth

CodeThrown when
MISSING_TOKENNo API key was provided or found in the environment.
DOWNLOAD_FAILEDdownload() could not fetch the signed URL.
NO_BASE64The runtime lacks a base64 decoder (used by s3delivery/ai).
ERRORA generic fallback when no more specific code applies.

Server-defined

The route handler and API may also surface their own codes verbatim, including:

CodeStatusMeaning
UNAUTHORIZED401Missing or invalid API key.
FILE_TOO_LARGE413Over the route or plan single-file limit.
TYPE_NOT_ALLOWED415Content type rejected by route constraints.
QUOTA_EXCEEDED403Upload would exceed your total storage quota.
NOT_FOUND404File does not exist, is deleted, or you cannot see it.
BAD_REQUEST400Malformed input.

Handling errors precisely

import { S3DeliveryError } from "s3delivery";

try {
  await upload();
} catch (e) {
  if (!(e instanceof S3DeliveryError)) throw e;
  switch (e.code) {
    case "FILE_TOO_LARGE":
    case "TYPE_NOT_ALLOWED":
    case "TOO_MANY_FILES":
      return showValidationError(e.message);
    case "ABORTED":
      return; // user cancelled — nothing to report
    case "QUOTA_EXCEEDED":
      return promptUpgrade();
    default:
      return reportUnexpected(e);
  }
}

validateFiles

The standalone client-side constraint check that the browser uploader and React hooks call internally. Pure and synchronous — safe to call anywhere, runs no I/O. Use it to validate a selection before you commit to an upload (for example, to disable a submit button).

import { validateFiles, S3DeliveryError } from "s3delivery";

function check(files: File[]) {
  try {
    validateFiles(files, {
      maxFiles: 10,
      maxFileSize: 25 * 1024 * 1024,
      allowedFileTypes: ["image/*", "application/pdf"],
    });
    return null;
  } catch (e) {
    return e instanceof S3DeliveryError ? e.message : "Invalid selection";
  }
}

It throws the first violation as an S3DeliveryError (TOO_MANY_FILES / FILE_TOO_LARGE / TYPE_NOT_ALLOWED), or returns nothing if the selection is valid.

UploadConstraints

interface UploadConstraints {
  /** Max number of files in a single upload call. */
  maxFiles?: number;
  /** Max size, in bytes, for any single file. */
  maxFileSize?: number;
  /** Allowed MIME types: exact (`"application/pdf"`) or wildcard (`"image/*"`). */
  allowedFileTypes?: string[];
}

Every field is optional; omit one to leave that dimension unconstrained. Type matching is case-insensitive, ignores a ; charset=… suffix, and treats "*" / "*/*" as allow-all. These same constraints (minus maxFiles) are enforced server-side by the route handler.

On this page