---
title: Overview
description: 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.
---

`files-sdk` gives the browser the same API the SDK gives the server. One binding — a [React hook](/ui/client/react), a [Vue composable](/ui/client/vue), or [Svelte stores](/ui/client/svelte) — mirrors every `Files` verb over a single HTTP endpoint:

<Tabs items={["React", "Vue", "Svelte"]}>

<Tab value="React">

```tsx
"use client";
import { useFiles } from "files-sdk/react";

export function Uploader() {
  const files = useFiles({ endpoint: "/api/files" });

  return (
    <>
      <input
        type="file"
        onChange={(e) => {
          const file = e.target.files?.[0];
          if (file) files.upload(file); // keyless — the server mints the key
        }}
      />
      {files.isUploading && <progress value={files.progress.fraction} />}
    </>
  );
}
```

</Tab>

<Tab value="Vue">

```vue
<script setup lang="ts">
import { useFiles } from "files-sdk/vue";

const files = useFiles({ endpoint: "/api/files" });

const onChange = (e: Event) => {
  const file = (e.target as HTMLInputElement).files?.[0];
  if (file) files.upload(file); // keyless — the server mints the key
};
</script>

<template>
  <input type="file" @change="onChange" />
  <progress
    v-if="files.isUploading.value"
    :value="files.progress.value.fraction"
  />
</template>
```

</Tab>

<Tab value="Svelte">

```svelte
<script lang="ts">
  import { useFiles } from "files-sdk/svelte";
  import { onDestroy } from "svelte";

  const files = useFiles({ endpoint: "/api/files" });
  const { isUploading, progress } = files;
  onDestroy(files.abort); // cancel in-flight calls on unmount

  const onChange = (e: Event) => {
    const file = (e.currentTarget as HTMLInputElement).files?.[0];
    if (file) files.upload(file); // keyless — the server mints the key
  };
</script>

<input type="file" on:change={onChange} />
{#if $isUploading}<progress value={$progress.fraction} />{/if}
```

</Tab>

</Tabs>

The browser never holds storage credentials. Calls go to **your** endpoint, which runs the SDK against whatever adapter you configured (S3, R2, GCS, Vercel Blob, …) and streams or signs as needed.

## The two halves

|            | Package                                                                                                                    | What it is                                                                                                                                                  |
| ---------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Client** | [`files-sdk/react`](/ui/client/react), [`files-sdk/vue`](/ui/client/vue), [`files-sdk/svelte`](/ui/client/svelte)          | A `useFiles` binding for your framework — every verb (imperative, with upload progress) plus optional reactive `useList` / `useFile` / `useSearch`.         |
| **Server** | `files-sdk/api` + a framework adapter ([Next.js](/ui/server/next), [Hono](/ui/server/hono), [Express](/ui/server/express)) | A [gateway](/ui/server/gateway) you mount at `/api/files` that exposes the `Files` API over HTTP, gated by an [`authorize`](/ui/server/authorization) hook. |

The same gateway backs all three bindings. A framework-agnostic core, `createFilesClient` from `files-sdk/client`, sits under them for non-framework (Node, worker) callers.

<Callout type="warn">
  The gateway proxies `download`, `list`, `delete`, and `move` to the browser —
  it is effectively a remote storage console. It is **deny-by-default**: nothing
  is exposed until you configure [`authorize`](/ui/server/authorization) or
  `operations`. Read that page before shipping.
</Callout>

## Quick start

**1. Mount the gateway.** Expose the `Files` API at an endpoint and scope every key to the signed-in user. This example uses Next.js; [Hono](/ui/server/hono) and [Express](/ui/server/express) are a one-liner too:

```ts title="app/api/files/route.ts" lineNumbers
import { createFiles } from "files-sdk";
import { s3 } from "files-sdk/s3";
import { createFilesRouter } from "files-sdk/api";
import { createRouteHandler } from "files-sdk/next";

const router = createFilesRouter({
  files: createFiles({ adapter: s3({ bucket: "uploads" }) }),
  allowedOrigins: ["https://app.example.com"],
  authorize: async ({ req }) => {
    const session = await auth(req); // throw → 401
    return { keyPrefix: `users/${session.id}/`, maxExpiresIn: 300 };
  },
});

export const { GET, POST, PUT } = createRouteHandler(router);
```

**2. Use your binding.** Drop the component above into your app — that's the whole loop: uploads stream directly to storage (or proxy through your endpoint for adapters that can't presign), and reads run against your gateway. Keys the client sends are relative to the authorized prefix, so the browser can never address another user's files. For a file browser, the reactive reads (`useList` / `useFile` / `useSearch`) wrap the read verbs with `data` / `isLoading` / `refetch`.

Next: pick your binding — [React](/ui/client/react), [Vue](/ui/client/vue), or [Svelte](/ui/client/svelte) — then set up the [gateway](/ui/server/gateway) and its [authorization](/ui/server/authorization).
