---
title: Vue
description: The full Files API in the browser as a Vue 3 composable - useFiles returns the verbs plus refs for ambient state, with reactive useList / useFile / useSearch.
related:
  - /guides/nuxt-file-upload
---

`files-sdk/vue` brings the full Files API to the browser as an idiomatic Vue 3 composable. `useFiles` returns one method per `Files` verb — `upload`, `download`, `url`, `list`, and the rest — plus ambient upload/error state as refs. The refs are nested in the returned object, so use `.value` in templates (as below) or destructure them first.

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

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

const onUpload = async (event: Event) => {
  const file = (event.target as HTMLInputElement).files?.[0];
  if (file) {
    await files.upload(file);
  }
};
</script>

<template>
  <input type="file" @change="onUpload" />
  <progress
    v-if="files.isUploading.value"
    :value="files.progress.value.fraction"
  />
  <p v-if="files.error.value">{{ files.error.value.message }}</p>
</template>
```

## The verbs

Every verb mirrors the SDK — `upload`, `download`, `head`, `exists`, `list`, `listAll`, `search`, `url`, `delete`, `copy`, `move`, `signedUploadUrl`, `capabilities`, plus the plugin verbs (`versions`, `restoreVersion`, `trashed`, `restoreTrashed`, `purge`) when the gateway exposes them (a missing one rejects with `Unsupported`) — including the bulk array forms. `search()`'s generator returns a `SearchSummary` (`{ truncated }`) saying whether the gateway's bounded walk stopped early, and a `416` rejects with `Invalid`. They are plain methods (no `.value`):

```ts lineNumbers
const { key } = await files.upload(file); // keyless → server mints the key
const stored = await files.download("report.pdf"); // → a lazy StoredFile
const link = await files.url("avatar.png"); // → string, for <img :src>
await files.delete(["a.txt", "b.txt"]); // bulk → { results, errors? }
```

## Ambient state (refs)

Upload progress and the last error are exposed as refs nested in the returned object. Use `.value` in templates, or destructure them (`const { uploads, error } = files`) so the template unwraps them. They stay reactive in script either way:

```ts lineNumbers
files.isUploading; // Ref<boolean> - true while any upload() is running
files.uploads; // Ref<FileUploadState[]> - one entry per file, across every upload() call
files.progress; // Ref<{ loaded, total, fraction }> over the current entries
files.error; // Ref<FilesError | undefined> - last error from any verb
files.reset(); // clear the error + finished uploads (re-arms after abort)
files.abort(); // abort every in-flight call
```

`uploads` accumulates one entry per file from every `upload()` call, including each item of a bulk `upload([...])`. Each entry keeps its position and is replaced by a fresh snapshot on every change. Its `status` ends as `"success"`, `"error"` (with `error` set), or `"aborted"` on every path. `reset()` leaves uploads that are still running in place. `error` also catches failures thrown while iterating `listAll()`/`search()`.

The composable owns an `AbortController` that is aborted automatically on scope dispose (component unmount), so in-flight requests are cancelled for you.

## Reactive reads

The query composables accept `MaybeRefOrGetter` inputs, so they re-run when their source changes — a file browser bound to a reactive `prefix` updates itself:

```ts lineNumbers
import { ref } from "vue";
import { useList, useFile, useSearch } from "files-sdk/vue";

const prefix = ref("docs/");
const list = useList(() => ({ prefix: prefix.value })); // re-runs when prefix changes
const file = useFile(selectedKey); // head() for a preview
const hits = useSearch(query, { match: "substring" });

// each returns refs:
list.data; // Ref<ListResult | undefined>
list.isLoading; // Ref<boolean>
list.error; // Ref<FilesError | undefined>
list.refetch(); // re-run on demand
```

`data` always belongs to the current input: when the source changes, the old result is dropped and `isLoading` is `true` until the new one arrives, and a disabled query (`useFile` with no key, or `enabled: false`) has no `data` or `error`. `refetch()` keeps the current `data` while it reloads, and keeps it if the reload fails. A `signal` in the options cancels the query's request too, which settles it with an `aborted` error.

Like the React versions they are cache-free and dependency-light; for shared caching, reach for a query library and call the imperative `useFiles()` methods inside it.

## Setting up the gateway

Point the composable at a mounted [gateway](/docs/ui/server/gateway) (`createFilesRouter` on Nitro/Nuxt, Next, Hono, Express, or any Web-`Request` runtime) and lock it down with [`authorize`](/docs/ui/server/authorization).
