React
One hook, every Files verb. Imperative methods with upload progress and ambient error state, plus optional reactive useList / useFile / useSearch.
files-sdk/react brings the full Files API to the browser as a React hook. useFiles returns one method per Files verb against the gateway you mounted, plus ambient upload/error state. Keys are plain strings (relative to your authorized prefix).
const files = useFiles({
endpoint: "/api/files", // default "/api/files"
headers: () => ({ authorization: token }), // sent on every call (static or lazy)
});The verbs
// Reads
await files.head("docs/a.txt"); // → StoredFile (lazy body)
await files.exists("docs/a.txt"); // → boolean
await files.list({ prefix: "docs/" }); // → { items, prefixes?, cursor? }
await files.url("img.png"); // → string (for <img src>)
await files.capabilities(); // → AdapterCapabilities
// Writes
await files.upload(file); // → { key, size, type, etag }
await files.delete("old.txt");
await files.copy("a.txt", "b.txt");
await files.move("a.txt", "c.txt");
// Streaming iterators
for await (const f of files.listAll()) {
/* every object */
}
for await (const f of files.search("docs/*.pdf")) {
/* glob/regex */
}Every verb mirrors the SDK's signature, including the bulk array forms — files.delete([a, b]), files.head([a, b]), files.download([a, b]) — which return the same partial-result shapes ({ deleted, errors? }, …) and never throw on a single bad key.
Uploading
upload has three forms, resolved by argument shape:
// Keyless - the server mints the key (the common case).
const { key } = await files.upload(file);
// Explicit key - you choose it (subject to the authorized prefix).
await files.upload("avatars/me.png", file, { contentType: "image/png" });
// Bulk - pooled at the configured concurrency.
await files.upload([
{ key: "a.txt", body: "one" },
{ key: "b.txt", body: "two" },
]);Track progress per call, or read the hook's ambient state:
await files.upload(file, {
onProgress: (p) => console.log(p.fraction), // 0 → 1
});
// or, ambient across any in-flight upload this hook started:
files.isUploading; // boolean
files.progress; // { loaded, total, fraction }
files.uploads; // per-file FileUploadState[]Downloading vs linking
Two tools, two jobs:
// download(key) → StoredFile: the bytes flow through your gateway. Works on
// every adapter. Use it when you need the data programmatically.
const file = await files.download("report.pdf");
const blob = await file.blob(); // lazy - only fetched on access
const text = await file.text();
const chunk = await files.download("video.mp4", {
range: { start: 0, end: 1023 },
});
// url(key) → string: a direct link. Use it for <img src> / <a href> / <video>.
// Only meaningful when the adapter can sign (capabilities.signedUrl.supported).
const src = await files.url("avatar.png");The returned StoredFile is the same lazy, File-shaped value the SDK returns — blob()/text()/arrayBuffer()/stream() plus key, size, type, etag, metadata.
Errors & cancellation
A thrown error is a FilesError (.code of "NotFound" | "Unauthorized" | "Conflict" | "ReadOnly" | "Provider"), rebuilt from the wire envelope. The last error is also mirrored to files.error for render-time display:
try {
await files.delete(key);
} catch (e) {
// e instanceof FilesError
}
{
files.error && (
<p>
{files.error.code}: {files.error.message}
</p>
);
}The hook owns an AbortController merged into every call. files.abort() cancels everything in flight; unmounting does the same automatically. files.reset() clears the ambient error/upload state and re-arms after an abort. Pass a per-call signal (or a hook-level signal option) to cancel selectively.
Reactive reads
For a file browser you usually want declarative state, not imperative calls. Three optional hooks wrap the read verbs with data / error / isLoading / isFetching / refetch, aborting their in-flight request on dependency change or unmount:
import { useList, useFile, useSearch } from "files-sdk/react";
const { data, isLoading, refetch } = useList({ prefix: "docs/" });
const file = useFile(selectedKey); // head() for a preview; disabled without a key
const hits = useSearch(query, { match: "substring" });They are deliberately cache-free and dependency-light. For shared caching, bring React Query or SWR and call useFiles() inside your queryFn — the imperative methods are designed for exactly that.
Outside React
The hook is a thin shell over createFilesClient from files-sdk/client — the same verb set, framework-agnostic, usable from Node, a worker, or a Vue/Svelte binding:
import { createFilesClient } from "files-sdk/client";
const client = createFilesClient({
endpoint: "https://app.example.com/api/files",
});
const file = await client.download("report.pdf");Overview
The whole Files API in the browser, over one endpoint — a React hook, a Vue composable, or Svelte stores, backed by a server gateway you mount in minutes.
Svelte
The full Files API in the browser, idiomatic Svelte - useFiles returns the verbs plus Svelte stores for ambient state (read with $store), with useList / useFile / useSearch.