---
title: copy
description: Copy an object from one key to another - server-side where the provider supports it, with a streaming read + write fallback otherwise.
---

`files.copy(from, to, options?)`

Copies the object at `from` to `to` and resolves to `void`. Where the provider has a native copy primitive (S3 `CopyObject`, GCS, Supabase, Vercel Blob, Dropbox, Azure, and others) the copy happens entirely server-side — no bytes travel through your process. Adapters without one fall back to a read + write, and a few providers can't copy at all and throw.

```ts lineNumbers
await files.copy("avatars/abc.png", "avatars/abc.bak.png");
```

Both keys are validated, and on a client with a [prefix](/prefixes) both `from` and `to` are resolved against it. The destination is overwritten if it already exists — there is no built-in guard against clobbering. The object's `contentType` and user `metadata` travel with the copy; `lastModified` is set to the time of the copy, since the destination is a fresh write rather than a clone of the source's timestamp. A missing `from` throws [`FilesError`](/api/errors) with `code: "NotFound"`.

## The read + write fallback

Adapters with no server-side copy primitive — UploadThing, Netlify Blobs, Cloudinary, and the R2 binding without HTTP credentials — emulate `copy` by downloading the source and re-uploading it to the destination. The source is streamed through rather than buffered, so even multi-GB copies stay within serverless memory limits, but two things differ from a native copy:

- **It costs an egress download plus an ingest upload.** For large or frequent copies on these providers, consider doing the copy at the application layer with a storage strategy that copies server-side.
- **It is not atomic.** Concurrent writes to `from` between the read and the write are not detected, so the destination may reflect an in-flight change to the source.

Each adapter's Compatibility section marks whether it copies natively, falls back, or throws.

## Providers without copy

Some backends have no way to copy to a caller-chosen key. Convex, for instance, assigns immutable storage ids, so `copy` throws `FilesError` with `code: "Provider"` rather than silently misbehaving. When you need the operation anyway, `download()` the source and `upload()` it back under a new key, or reach for the [escape hatch](/escape-hatch).

## Options

`copy` accepts the shared `OperationOptions` — `signal`, `timeout`, and `retries`. As elsewhere, only `Provider` failures are [retried](/retries); deterministic errors like `NotFound` are returned immediately.

```ts lineNumbers
await files.copy("avatars/abc.png", "avatars/abc.bak.png", {
  signal: controller.signal,
  retries: 3,
});
```

## On a `FileHandle`

[`files.file(key)`](/api/file) exposes the same operation bound to one key, as `copyTo` (the handle is the source) and `copyFrom` (the handle is the destination):

```ts lineNumbers
const avatar = files.file("avatars/abc.png");

await avatar.copyTo("avatars/abc.bak.png"); // copy(key, "avatars/abc.bak.png")
await avatar.copyFrom("uploads/new.png"); // copy("uploads/new.png", key)
```
