transfer
Stream every object under a prefix from one Files instance to another - the cross-provider migration that the unified surface uniquely enables, built entirely on listAll, download, and upload.
copy and move live inside a single adapter. A migration spans two — and that's the one thing a unified surface uniquely enables. transfer(source, dest, options?) walks every object the source exposes and streams each one straight to the dest, whatever the backends are.
import { Files, transfer } from "files-sdk";
import { s3 } from "files-sdk/s3";
import { r2 } from "files-sdk/r2";
const from = new Files({ adapter: s3({ bucket: "old" }) });
const to = new Files({
adapter: r2({ bucket: "new", accountId, accessKeyId, secretAccessKey }),
});
const { transferred, errors } = await transfer(from, to, {
prefix: "uploads/",
onProgress: ({ done, total, key }) =>
console.log(`${done}/${total} — ${key}`),
});Both arguments are full Files instances, not raw adapters, so each leg honors its own instance's prefix, retries, timeouts, and hooks. Each object is streamed download-to-upload, so the destination never sees a buffered copy of a large file.
transfer is a one-shot copy. For an incremental, optionally-pruning mirror — skip-unchanged, delete extraneous keys, dry-run the plan — reach for sync instead.
What travels
The body, content type, and user metadata move with each object. Destination-assigned fields (etag, lastModified) are fresh on the other side, and Cache-Control is not carried — a StoredFile doesn't expose it. Metadata is dropped for adapters with no metadata primitive; a metadata key a destination adapter rejects outright (Bunny, Appwrite, PocketBase) surfaces as a per-key error rather than failing the whole run.
Result shape
Like the bulk actions, transfer does not throw on a partial failure. Successes, skips, and failures come back separated, in walk order:
const { transferred, skipped, errors } = await transfer(from, to);| Field | Contents |
|---|---|
transferred | Source keys copied to the destination. |
skipped | Keys skipped because they already existed. Omitted when none. |
errors | Per-key { key, error } failures. Omitted when every key wins. |
error is always a normalized FilesError.
Options
await transfer(from, to, {
prefix: "uploads/", // only walk keys under this prefix
transformKey: (key) => `archive/${key}`, // remap each key for the destination
overwrite: false, // skip keys already at the destination
concurrency: 16, // keys in flight at once (default 8)
limit: 500, // page size for the underlying walk
stopOnError: true, // bail at the first failure
signal: controller.signal, // abort the whole transfer
onProgress: ({ done, key, status }) => {},
});transformKey maps the logical key — each instance applies its own prefix independently — which makes re-homing under a new namespace (or moving between two prefixed instances) a one-liner. With overwrite: false, every key costs one extra exists() against the destination.
concurrency bounds how many objects stream at once (and therefore memory, since each in-flight key holds one open stream). It's ignored under stopOnError, which runs sequentially and returns the keys transferred so far plus the first error. signal is forwarded to every list / exists / download / upload and stops new keys from being scheduled; keys already in flight may still finish or surface as errors.
Progress
onProgress fires once per key as it settles, carrying a running done count, the total, the key, and whether it was transferred or skipped. The source is walked in full before any transfer begins, so total is the final denominator from the very first event.
await transfer(from, to, {
onProgress: ({ done, total, key, status }) => {
console.log(`${done}/${total}: ${key} (${status})`);
},
});sync
Mirror one Files instance onto another - skip-unchanged, prune extraneous keys, and dry-run the plan. The incremental, optionally-pruning sibling of transfer, built on the same primitives.
copy
Copy an object from one key to another - server-side where the provider supports it, with a streaming read + write fallback otherwise.