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
}codeis 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 samecode, so client and server agree.statusis present when the error came from an HTTP response (e.g.413forFILE_TOO_LARGEfrom the server).messageis 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)
| Code | Thrown when |
|---|---|
TOO_MANY_FILES | More files than maxFiles were selected. |
FILE_TOO_LARGE | A file exceeds maxFileSize. |
TYPE_NOT_ALLOWED | A file's MIME type is not in allowedFileTypes. |
NO_FILES | An upload was attempted with an empty selection. |
Transport / engine
| Code | Thrown when |
|---|---|
UPLOAD_FAILED | A storage PUT returned a non-2xx status. |
NETWORK_ERROR | The request failed at the network layer. |
ABORTED | The upload was cancelled via an AbortSignal / control.abort(). |
NO_ETAG | A multipart part response exposed no ETag (usually a CORS config gap). |
Client / auth
| Code | Thrown when |
|---|---|
MISSING_TOKEN | No API key was provided or found in the environment. |
DOWNLOAD_FAILED | download() could not fetch the signed URL. |
NO_BASE64 | The runtime lacks a base64 decoder (used by s3delivery/ai). |
ERROR | A generic fallback when no more specific code applies. |
Server-defined
The route handler and API may also surface their own codes verbatim, including:
| Code | Status | Meaning |
|---|---|---|
UNAUTHORIZED | 401 | Missing or invalid API key. |
FILE_TOO_LARGE | 413 | Over the route or plan single-file limit. |
TYPE_NOT_ALLOWED | 415 | Content type rejected by route constraints. |
QUOTA_EXCEEDED | 403 | Upload would exceed your total storage quota. |
NOT_FOUND | 404 | File does not exist, is deleted, or you cannot see it. |
BAD_REQUEST | 400 | Malformed 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.