uploads

Accept file uploads with size limits, store them on disk or in the cloud, and show progress in the browser.

BackendClient Server

Every export of kitr/uploads, kitr/uploads/react, kitr/uploads/server, kitr/uploads/provider, read from the published type definitions. The guide shows them in use.

kitr/uploads

accepts

function accepts(type: string, name: string, accept?: string[]): boolean;

image/* matches image/png; exact types match themselves; .pdf matches by extension.

checkFile

function checkFile(file: File, rules?: UploadRules): string | null;

Client-side check against the same rules the server enforces, so errors show before the request.

formatBytes

function formatBytes(bytes: number): string;

upload

function upload(files: File | File[], options?: UploadOptions): Promise<UploadedFile[]>;

Upload one or more files with progress. XMLHttpRequest is the only browser API that reports upload progress, so this is XHR with a promise around it; a fetch fallback runs elsewhere.

UploadError

class UploadError extends Error {
  readonly status: number;
  readonly body?: unknown | undefined;
  constructor(message: string, status: number, body?: unknown | undefined);
}

defineConfig

const defineConfig: <const C extends UploadsConfig>(config: C) => C;

UploadedFile

interface UploadedFile {
  id: string;
  name: string;
  size: number;
  type: string;
  /** Where it can be fetched, when the driver serves files. */
  url?: string;
  /** The storage key the driver wrote. */
  key: string;
}

What the server returns for every stored file, and what the client resolves with.

UploadOptions

interface UploadOptions {
  /** The endpoint, default `/api/uploads`. */
  url?: string;
  /** Form field name, default `file`. */
  field?: string;
  /** Extra fields sent with the file. */
  fields?: Record<string, string>;
  headers?: Record<string, string>;
  onProgress?: (progress: {
    loaded: number;
    total: number;
    percent: number;
  }) => void;
  signal?: AbortSignal;
  /** Sent with credentials (cookies). Default true. */
  credentials?: boolean;
}

UploadRules

interface UploadRules {
  /** Bytes. Default 10 MiB. */
  maxSize?: number;
  /** Allowed MIME types or patterns such as `image/*`. Default: anything. */
  accept?: string[];
  /** Files per request. Default 1. */
  maxFiles?: number;
}

UploadsConfig

interface UploadsConfig extends UploadRules {
  /** Where files go. Default: `storage/uploads` on disk (`createDiskUploadsDriver`). */
  driver?: unknown;
  /** Public URL prefix the driver answers under, e.g. `/uploads`. */
  publicPath?: string;
}

kitr/uploads/react

useUpload

function useUpload(options?: UploadOptions & UploadRules): {
  send: (input: FileList | File[] | File | null) => Promise<UploadedFile[]>;
  cancel: () => void | undefined;
  reset: () => void;
  files: UploadedFile[];
  progress: number;
  uploading: boolean;
  error: Error | null;
};

Upload with progress from a component:

const { send, progress, uploading, error } = useUpload({ accept: ['image/*'] })
<input type="file" onChange={(e) => send(e.target.files)} />

UseUploadState

interface UseUploadState {
  files: UploadedFile[];
  progress: number;
  uploading: boolean;
  error: Error | null;
}

kitr/uploads/server

createDiskUploadsDriver

function createDiskUploadsDriver(options?: {
  dir?: string;
  publicPath?: string;
}): UploadsDriver;

Files under a directory (default storage/uploads), loaded lazily so the browser bundle never sees node:fs.

createMemoryUploadsDriver

function createMemoryUploadsDriver(): UploadsDriver;

createUploads

function createUploads(config?: UploadsConfig): Uploads;

mountUploads

function mountUploads<S extends {
  h3: any;
  app: KitrApp;
}>(server: S, path?: string): S;

Mount POST <path> (receive) and GET <path>/** (serve) on a kitr server from server/main.ts. The service is resolved per request, so this can run before the app has booted.

useUploads

function useUploads(app: KitrApp): Uploads;

StoredFile

interface StoredFile {
  key: string;
  name: string;
  size: number;
  type: string;
}

Uploads

interface Uploads {
  /** Read a multipart request, apply the rules and store the files. Throws KitrError on violation. */
  receive(event: H3Event, rules?: UploadRules & {
    field?: string;
  }): Promise<UploadedFile[]>;
  /** Store bytes directly (from a job, a migration). */
  store(file: {
    name: string;
    type: string;
    data: Uint8Array;
  }): Promise<UploadedFile>;
  read(key: string): Promise<{
    data: Uint8Array;
    type: string;
  } | undefined>;
  remove(key: string): Promise<void>;
  /** Mount `POST <path>` (receive) and `GET <path>/:key` (serve) on a kitr server. */
  mount<S extends {
    h3: any;
    app?: KitrApp;
  }>(server: S, path?: string): S;
  readonly driver: UploadsDriver;
}

UploadsDriver

interface UploadsDriver {
  name: string;
  put(key: string, data: Uint8Array, meta: {
    type: string;
    name: string;
  }): Promise<void>;
  get(key: string): Promise<{
    data: Uint8Array;
    type: string;
  } | undefined>;
  delete(key: string): Promise<void>;
  /** A URL for the key, when files are served (CDN, signed URL, the kitr route). */
  url?(key: string): string | Promise<string>;
}

Where bytes go: disk, S3, R2, GCS. Keys are relative paths the driver owns.

kitr/uploads/provider

uploadsProvider

const uploadsProvider: import("@kitr/core").KitrProvider;

Binds uploads on the server from config/uploads.ts. The server module is loaded at boot so the browser bundle of this provider never resolves node:fs.