move
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.
await files.move("uploads/tmp-abc.png", "avatars/user-123.png");Both keys are validated, and on a client with a prefix both from and to are resolved against it. The destination is overwritten if it already exists. A missing from throws FilesError 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) renames in place, and Cloudinary uses its server-side rename (same asset_id, no re-upload). Everywhere else it falls back to copy then 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
moverecovers — the copy overwrites and the delete is idempotent — but there is no transactional guarantee in between. - It inherits
copy's costs. On providers whosecopyis a read + write rather than server-side (UploadThing, Netlify Blobs, Cloudinary, FTP, SFTP, …) the bytes travel through your process once before the source is deleted. Seecopyfor the details.
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, 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; deterministic errors like NotFound are returned immediately.
await files.move("uploads/tmp-abc.png", "avatars/user-123.png", {
signal: controller.signal,
retries: 3,
});On a FileHandle
files.file(key) exposes the same operation bound to one key, as moveTo (the handle is the source) and moveFrom (the handle is the destination):
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)