---
title: move
description: Rename a key - a native, atomic rename where the provider has one, with a copy + delete fallback everywhere else.
---

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

Moves (renames) the object at `from` to `to` and resolves to `void`.

```ts lineNumbers
await files.move("uploads/tmp-abc.png", "avatars/user-123.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. A missing `from` throws [`FilesError`](/api/errors) with `code: "NotFound"`.

## How it moves

Where the provider exposes a native rename that's atomic or avoids re-transferring the body, `move` uses it directly: the local filesystem ([`fs`](/adapters/fs)) renames in place, [FTP](/adapters/ftp) and [SFTP](/adapters/sftp) issue a native rename (`RNFR`/`RNTO` and the SFTP `RENAME` op — no body round-trip), and Cloudinary uses its server-side `rename` (same `asset_id`, no re-upload). Everywhere else it falls back to [`copy`](/api/copy) then [`delete`](/api/delete): the source is copied to the destination and the original is removed.

Object stores (S3, R2, GCS, Azure, …) have no atomic move primitive, so they always take the copy + delete path. Two consequences follow from the fallback:

- **It is not atomic.** A crash or failure between the copy and the delete can leave the object at _both_ keys. Re-running `move` recovers — the copy overwrites and the delete is idempotent — but there is no transactional guarantee in between.
- **It inherits `copy`'s costs.** On providers whose `copy` is a read + write rather than server-side (UploadThing, Netlify Blobs, …) the bytes travel through your process once before the source is deleted. See [`copy`](/api/copy#the-read--write-fallback) for the details. (FTP and SFTP avoid this for `move` — they rename natively — but their standalone `copy` is still a read + write.)

## Moving onto the same key

Moving a key onto itself (`from === to`, after the client `prefix` is applied) is a **no-op** — nothing is copied or deleted. This guard matters: without it, the copy + delete fallback would copy the object onto itself and then delete it, destroying the file.

## Providers without move

`move` works on every adapter except where its building blocks don't. Since the fallback is `copy` + `delete`, `move` throws wherever `copy` throws — notably [Convex](/adapters/convex), which assigns immutable storage ids and has no rename. There, `download()` the source and `upload()` it under a new key, then track the new id. Each adapter's Compatibility section marks `copy` (and therefore `move`).

## Options

`move` 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.move("uploads/tmp-abc.png", "avatars/user-123.png", {
  signal: controller.signal,
  retries: 3,
});
```

## On a `FileHandle`

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

```ts lineNumbers
const tmp = files.file("uploads/tmp-abc.png");

await tmp.moveTo("avatars/user-123.png"); // move(key, "avatars/user-123.png")

const avatar = files.file("avatars/user-123.png");
await avatar.moveFrom("uploads/tmp-abc.png"); // move("uploads/tmp-abc.png", key)
```
