Listing & metadata
Enumerate objects with list() — prefixes, pagination via cursor and hasMore, delimiter "folders" — plus getMetadata() and exists() for inspecting single objects.
Three methods let you inspect what's in your bucket without downloading bytes:
list() enumerates objects, getMetadata() inspects one, and exists() checks
presence.
Listing objects
list() returns a single page of objects under an optional prefix. Call it
with no arguments to list everything, or pass a prefix to scope it.
const page = await storage.list("avatars/");
for (const obj of page.objects) {
console.log(obj.key, obj.size, obj.lastModified);
}Each object is an ObjectSummary:
keystringThe object's full key.
sizenumberSize in bytes.
etagstring | undefinedThe entity tag, where the provider reports it.
lastModifiedDate | undefinedLast-modified time, where the provider reports it.
Pagination
list() never returns everything at once — providers cap page size (S3 at 1000).
The result tells you whether there's more and how to get it:
objectsObjectSummary[]The objects in this page.
prefixesstring[]Common "folder" prefixes — populated only when you pass a delimiter.
cursorstring | undefinedAn opaque continuation token. Pass it back as options.cursor for the next
page. undefined when there are no more pages.
hasMorebooleantrue when more objects exist beyond this page.
Loop until hasMore is false, feeding cursor back in:
let cursor: string | undefined;
const all = [];
do {
const page = await storage.list("uploads/", { limit: 500, cursor });
all.push(...page.objects);
cursor = page.cursor;
} while (cursor);limitnumberMax objects per page. Providers cap this (S3 at 1000); Radon forwards it and
reports the rest via cursor / hasMore.
cursorstringThe continuation token from a previous page's cursor.
delimiterstringGroup keys sharing a prefix up to this character into prefixes instead of
returning every key — a "list folders, not files" view.
Folders with a delimiter
Object stores are flat — there are no real folders, just keys with slashes. A
delimiter (usually "/") makes list() collapse everything below the next
slash into a common prefix, so you can browse a hierarchy one level at a
time.
const page = await storage.list("uploads/", { delimiter: "/" });
page.prefixes; // ["uploads/2025/", "uploads/2026/"] — the "folders"
page.objects; // only keys directly under "uploads/", not inside sub-prefixesgetMetadata()
getMetadata() fetches everything about one object except its bytes — size,
content type, timestamps, and your user metadata.
const meta = await storage.getMetadata("avatars/ada.png");
meta.size; // bytes
meta.contentType; // "image/png"
meta.lastModified; // Date
meta.etag; // entity tag
meta.cacheControl; // stored Cache-Control, if any
meta.metadata; // your user metadata, e.g. { customerId: "cus_123" }keystringsizenumbercontentTypestring | undefinedetagstring | undefinedlastModifiedDate | undefinedcacheControlstring | undefinedCache-Control, if any.contentDispositionstring | undefinedContent-Disposition, if any.versionIdstring | undefinedObject version id, on versioned buckets.
metadataRecord<string, string>Your user metadata, echoed back.
rawunknownThe untouched provider response.
getMetadata throws when the object is missing
If the key doesn't exist, getMetadata() throws ObjectNotFoundError (with
.provider and .key). Use exists() when you only need a yes/no and don't
want to catch an error.
exists()
exists() returns a plain boolean — true if an object is at that key, false
otherwise. It never throws for a missing object.
if (await storage.exists("avatars/ada.png")) {
// ...
}Under the hood this is a HEAD request on the S3 family (cheap — no body), and
the provider's nearest equivalent elsewhere.
Targeting a provider
Like every operation, list, getMetadata, and exists accept a { provider }
option to override the default:
await storage.list("uploads/", { provider: "r2", limit: 100 });list() uses the primary provider under failover
With a failover chain configured, list() always
targets the primary provider — it doesn't merge listings across the chain. Pass
{ provider } to list a specific backend.
Failover
An ordered chain of storage providers, tried in turn until one succeeds — what fails over, what doesn't (getUrl and list), and how AllProvidersFailedError works.
Providers
All 26 storage providers — required credentials and env vars, defaults, addressing, and quirks. Three are free (S3, R2, local); the rest are Pro.