Skip to content
Shiny Controls v1.0 - The Ultra Control Suite for .NET MAUI & BlazorO...M...G!

IndexedDB (Blazor WASM)

The Shiny.DocumentDb.IndexedDb package provides an IndexedDB-backed document store for Blazor WebAssembly applications. It implements the full IDocumentStore interface using JavaScript interop — no native dependencies, no file system access required.

NuGet package Shiny.DocumentDb.IndexedDb
  • Blazor WebAssembly apps that need client-side persistent storage
  • Offline-capable progressive web apps (PWAs)
  • Browser-based apps where you want the same document store API as your server/mobile code
Terminal window
dotnet add package Shiny.DocumentDb.IndexedDb
Changed files
  • MyApp/
MyApp.csproj
MyApp.csproj
1<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">23    <PropertyGroup>4        <TargetFramework>net10.0</TargetFramework>5        <Nullable>enable</Nullable>6        <ImplicitUsings>enable</ImplicitUsings>7        <MicrosoftVersion>10.0.10</MicrosoftVersion>8        <ShinyClientVersion>5.5.0</ShinyClientVersion>9        <ShinyControlsVersion>1.2.0</ShinyControlsVersion>10        <ShinyExtensionsLocalizationVersion>2.0.1</ShinyExtensionsLocalizationVersion>11        <ShinyExtensionsStoresVersion>5.1.5</ShinyExtensionsStoresVersion>12        <ShinyExtensionsReflectorVersion>5.1.5</ShinyExtensionsReflectorVersion>13        <ShinyExtensionsDIVersion>5.1.5</ShinyExtensionsDIVersion>14        <ShinyExtensionsBlazorHostingVersion>5.1.5</ShinyExtensionsBlazorHostingVersion>15        <ShinySpeechVersion>3.0.0-beta-0030</ShinySpeechVersion>16        <ShinyAiConversationVersion>3.0.0-beta-0030</ShinyAiConversationVersion>17        <ShinyDocumentDbVersion>13.4.0</ShinyDocumentDbVersion>18        <MediatorVersion>6.8.0</MediatorVersion>19    </PropertyGroup>2021    <ItemGroup>22        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="$(MicrosoftVersion)" />23        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="$(MicrosoftVersion)" PrivateAssets="all" />2425        <PackageReference Include="Shiny.DocumentDb.IndexedDb" Version="$(ShinyDocumentDbVersion)" />2627    </ItemGroup>2829</Project>
  1. Register with dependency injection in your Blazor WASM Program.cs:

    using Shiny.DocumentDb.IndexedDb;
    builder.Services.AddSingleton(new IndexedDbDocumentStoreOptions { DatabaseName = "MyAppDb" });
    builder.Services.AddSingleton<IDocumentStore, IndexedDbDocumentStore>();

    Or with full options:

    var options = new IndexedDbDocumentStoreOptions
    {
    DatabaseName = "MyAppDb",
    Version = 1
    };
    options.ConfigureDocument<User>(cfg => cfg.ToStore()); // dedicated object store
    options.ConfigureDocument<Order>(cfg => cfg.ToStore("orders")); // explicit store name
    builder.Services.AddSingleton(options);
    builder.Services.AddSingleton<IDocumentStore, IndexedDbDocumentStore>();
  2. Inject and use IDocumentStore in your components:

    @inject IDocumentStore Store
    @code {
    private List<User> users = new();
    protected override async Task OnInitializedAsync()
    {
    users = (await Store.Query<User>().ToList()).ToList();
    }
    private async Task AddUser()
    {
    var user = new User { Name = "Alice", Age = 25 };
    await Store.Insert(user);
    users.Add(user);
    }
    }
Property Type Default Description
DatabaseName string (required) The name of the IndexedDB database
Version int 1 IndexedDB database version. Increment when adding new object stores
StoreName string "documents" Default object store for unmapped types
TypeNameResolution TypeNameResolution ShortName How type names are stored
JsonSerializerOptions JsonSerializerOptions? null JSON serialization settings
UseReflectionFallback bool true Set to false for AOT safety
Logging Action<string>? null Diagnostic callback

Similar to table-per-type mapping in SQL providers, you can map document types to dedicated IndexedDB object stores:

var options = new IndexedDbDocumentStoreOptions
{
DatabaseName = "MyAppDb"
};
options.ConfigureDocument<User>(cfg => cfg.ToStore()); // auto-derived store name
options.ConfigureDocument<Order>(cfg => cfg.ToStore("orders")); // explicit store name
options.ConfigureDocument<Sensor>(cfg => // custom Id property
{
cfg.ToStore();
cfg.MapIdProperty(s => s.DeviceKey);
});
builder.Services.AddSingleton(options);
builder.Services.AddSingleton<IDocumentStore, IndexedDbDocumentStore>();

The IndexedDB provider supports the full IDocumentStore API:

  • Insert, Update, Upsert, BatchInsert
  • Get, Remove, Clear
  • SetProperty, RemoveProperty
  • GetDiff (JSON patch diffing)
  • Query<T>() fluent LINQ queries (Where, OrderBy, Paginate, Select, aggregates)
  • Count<T>()
  • store.OpenSession() + SaveChanges() (operations are batched in a single IndexedDB transaction)
  • Raw SQL queriesQuery<T>(string whereClause) and QueryStream<T>(string whereClause) throw NotSupportedException. Use the LINQ-based Query<T>() overload instead.
  • Spatial queries — IndexedDB does not support R*Tree or GeoJSON indexes.

Under the hood, the provider:

  1. Opens an IndexedDB database with the configured name and version
  2. Creates object stores (with a typeName index) during the upgradeneeded event
  3. Stores documents as { key, id, typeName, data, createdAt, updatedAt } records
  4. Uses the composite key "{TypeName}:{Id}" as the IndexedDB key path
  5. Queries load all documents of a type via the typeName index, then apply LINQ predicates client-side

All IndexedDB operations are performed through a JavaScript ES module (shiny-indexeddb.js) bundled as a static web asset.

If you need SQL-level query features (raw WHERE clauses, JSON indexes, spatial queries), the existing SQLite provider is also WASM-compatible when paired with SQLitePCLRaw.bundle_wasm:

  • WAL pragma is automatically skipped on OperatingSystem.IsBrowser()
  • Spatial (R*Tree) is automatically disabled in the browser
  • Backup() is marked [UnsupportedOSPlatform("browser")]
  • Use Data Source=:memory: or Emscripten OPFS-mounted paths

The IndexedDB provider is lighter (no native WASM SQLite binary) and simpler for most client-side storage scenarios. Choose SQLite-in-WASM only when you need its advanced query capabilities.

When you add new type-to-store mappings, increment Version so that the upgradeneeded event fires and creates the new object stores:

opts.Version = 2; // was 1, now creating a new store
opts.ConfigureDocument<NewType>(cfg => cfg.ToStore(cfg.TypeName));

IndexedDB handles the schema migration automatically — existing data in other stores is preserved.

IndexedDB supports temporal historyMapTemporal<T> records every version to a {store}_history object store, read back via the ITemporalDocumentStore capability interface (History/AsOf/Restore/GetDiffBetween/AsOfAll/…). Resolve or cast to ITemporalDocumentStore; the methods are not on the base IDocumentStore.

Because temporal adds new object stores, bump Version when adding cfg.MapTemporal to an already-deployed database so the upgrade creates the history stores (a fresh database needs no change):

opts.Version = 2; // was 1 — schema upgrade creates the {store}_history object stores
opts.ConfigureDocument<Order>(cfg => cfg.MapTemporal());