Skip to content

Platform · Storage

Choosing where the file lives

Storage is a public contract, not an implementation detail: OPFS, IndexedDB, Cache Storage and in-memory are interchangeable providers behind the same interface. Selection prefers the order you configure, but existing data outranks preference - a database created on IndexedDB does not silently vanish the day OPFS becomes available.

Client database OPFS

Fastest persistent tier. Real files in the origin private file system. Concurrent connections, no COOP/COEP.

Configure

Preference order, and what counts as acceptable

Every registered provider is a candidate. AllowNonPersistentFallback() is what makes in-memory acceptable at all - without it, an origin with no persistent backend fails to open rather than quietly losing data on reload.

Try The session bar above is this API in action: Move data migrates the image, Start empty binds a fresh file without copying.
csharp Program.cs
BsqliteStorageSelectionBuilder.Create(s => s
    .Prefer(BsqliteOpfsStorageProvider.ProviderName)
    .Fallback(BsqliteIndexedDbStorageProvider.ProviderName)
    .Fallback(BsqliteCacheStorageProvider.ProviderName)
    .Fallback(BsqliteInMemoryStorageProvider.ProviderName)
    .AllowNonPersistentFallback());   // without this, in-memory is not acceptable

// Default is BsqliteStorageMigrationMode.KeepExisting: data outranks preference.
// AutomaticOnOpen copies to the preferred backend, checks the image, then rebinds.
csharp Migrating an existing database
Inspect

Why this session landed where it did

A resolution is a record of the whole decision, not just its outcome: every candidate, its probe, and the reason it was passed over. That is what makes a support question answerable without reproducing the user's browser.

Live

Opening this tab's database…

csharp BsqliteStorageResolution
var session = await database.OpenAsync();
var resolution = session.Resolution;

resolution.Provider.Name;                 // the backend that actually opened
resolution.IsFirstChoice;                 // false when a fallback won
resolution.WasDecidedByExistingData;      // the sticky binding overruled preference
resolution.BetterProviderAvailable?.Name; // ranked higher but holds no data
resolution.Attempts;                      // every candidate, with the reason it lost
Capabilities

What this backend can honour

The core enforces these flags; it never special-cases OPFS against IndexedDB in SQL. A backend that cannot honour ATTACH or PRAGMA page_size fails in BsqliteFeatureGuards instead of corrupting the file - see Limits.

Live

Opening this tab's database…

csharp Provider capabilities
var caps = session.Resolution.Provider.Capabilities;

caps.IsPersistent;                       // survives reload
caps.SupportsMultipleConnections;
caps.SupportsConcurrentReads;
caps.SupportsRelaxedDurability;
caps.SupportsMultiDatabaseTransactions;  // gates ATTACH
caps.CanChangePageSize;                  // gates PRAGMA page_size

// The core reads these flags. It never special-cases a provider by name, so a new
// backend gets the same guards without touching Bsqlite.
Probe

Ask every backend what it can do here

A probe reports availability, quota when the browser will say, and the raw environment facts behind the verdict. Unavailable is a result, never an exception, so selection can continue and still explain itself afterwards.

Try Probe now, then compare the numbers after importing a larger database on Database files.
Live
csharp Probing providers
foreach (var provider in Database.Providers)
{
    var probe = await provider.ProbeAsync();

    probe.IsAvailable;         // unavailability is a result, never an exception
    probe.UnavailableReason;   // why, in one sentence
    probe.UsageBytes;          // when the browser will say
    probe.QuotaBytes;
    probe.Environment;         // raw facts: JSPI, getDirectory, isSecureContext...
}
Permission

Ask the browser not to evict you

Persistent storage is granted per origin, not per backend. Without it, any of the persistent backends may be cleared under storage pressure - which is a product decision, so the library exposes the call rather than making it for you.

Live
Currently: not requested yet
csharp Persistent storage
// navigator.storage.persist(). Origin-wide, not per backend: without it the browser
// may evict OPFS / IndexedDB / Cache Storage under pressure.
bool granted = await BsqlitePersistence.RequestAsync(js);

The four backends

Every provider this site registers, in its preference order. Each lives in a namespace of its own and is registered with AddStorageProvider<T>().

ProviderCharacter
OPFS opfs Fastest persistent tier. Real files in the origin private file system. Concurrent connections, no COOP/COEP.
IndexedDB indexeddb Widest reach. Survives browsers that do not have OPFS. Relaxed durability available. Page size is pinned.
Cache Storage cache-storage The besql-compatible fallback. Persistence cost scales with database size.
In-memory in-memory Volatile. Gone on reload. Useful to try the engine without touching disk.
An unhandled error has occurred. Reload 🗙