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.
Pro feature
Multi-provider failover is a Radon Pro feature. Configuring a failover
chain requires a licenseKey and await storage.init() — otherwise you get
LicenseRequiredError.
Failover gives you an ordered chain of providers. Each operation tries the first provider; if it fails, Radon transparently retries the next, and so on, returning the first success. It's redundancy for your storage layer — if S3 has an outage, writes and reads keep flowing to your backup.
The chain
Set failover to an ordered list of provider slugs. Each must also appear in
providers.
const storage = new RadonStorage({
providers: {
s3: { bucket: "primary", region: "us-east-1" },
r2: { bucket: "backup", accountId: process.env.CF_ACCOUNT },
},
failover: ["s3", "r2"], // try s3, fall back to r2
licenseKey: process.env.RADON_LICENSE_KEY,
});
await storage.init();
// Lands on the first provider that accepts it — s3, or r2 if s3 fails:
await storage.upload({ key: "a.png", body: buf });The head of the chain (s3 here) becomes the default provider, so you don't
need to set defaultProvider separately.
What fails over
Every read and write operation flows through the chain:
uploaddownloaddeleteexistsgetMetadatacopy
Each tries providers in order until one succeeds.
What does NOT fail over
getUrl and list use the primary provider only
getUrl() and list() never fail over — they always target the primary
(head-of-chain) provider. Both are deterministic, per-provider operations: a
signed URL is only valid for the store that minted it, and a listing is
meaningful only for one bucket. Failing them over would produce dead links or
merged, confusing results.
Resumable/multipart uploads also stay on the primary provider — a multipart session is bound to the store that opened it.
Bypassing the chain
Name a provider explicitly and that call skips failover entirely — it targets exactly that provider:
await storage.upload({ key: "a.png", body: buf }, { provider: "s3" }); // s3 only, no fallbackThis is how you write to a specific store even when a chain is configured.
When every provider fails
If all providers in the chain fail, Radon throws
AllProvidersFailedError. It carries each underlying error, in the order the
providers were tried, so you can see exactly what went wrong where.
import { AllProvidersFailedError } from "@radonsdk/storage";
try {
await storage.upload({ key: "a.png", body: buf });
} catch (err) {
if (err instanceof AllProvidersFailedError) {
for (const { provider, error } of err.errors) {
console.error(`${provider} failed:`, error);
}
}
}errors{ provider: string; error: unknown }[]One entry per attempted provider, in chain order, each pairing the provider slug with the error it threw.
Failover vs. explicit backup
Failover retries on any failure — including a genuine client error like an
object that doesn't exist. If provider A returns "not found" and provider B has
the object, a download will succeed from B, which is usually what you want for
redundancy. Keep this in mind for operations where "not found" is a meaningful
answer: prefer naming the provider explicitly if you need to distinguish
"missing on the primary" from "missing everywhere".
Next steps
Resumable uploads
Chunked multipart uploads for large files — the uploadResumable() helper, the low-level multipart primitives, part sizes, and which providers support them.
Listing & metadata
Enumerate objects with list() — prefixes, pagination via cursor and hasMore, delimiter "folders" — plus getMetadata() and exists() for inspecting single objects.