Skip to content
Introducing AI Conversations: Natural Language Interaction for Your Apps! Learn More

Stores

Persist class properties across sessions with a single line of code. Shiny Stores provides a cross-platform key/value store abstraction that works on iOS, Android, Windows, and Blazor WebAssembly. Swap between Preferences, Secure Storage, Local Storage, and more — all behind the same API.

Frameworks
.NET
Blazor
Operating Systems
Android
iOS
Windows
GitHubGitHub stars for shinyorg/extensions
DownloadsNuGet downloads for Shiny.Extensions.Stores
  • Unified key/value store interface across all platforms
  • Built-in stores for Preferences, Secure Storage, Local Storage, Session Storage, and In-Memory
  • Source-generated property binding — mark partial properties with [Bind] and the generator emits getter/setter bodies that round-trip through the store. No INotifyPropertyChanged, no reflection
  • Static Shiny.Stores accessor for direct Stores.Default.Set/Get, Stores.Secure.Set/Get, and arbitrary keyed lookups
  • Custom store support — implement IKeyValueStore for your own storage backend
  1. Install the NuGet package:

    Terminal window
    dotnet add package Shiny.Extensions.Stores
  2. Register stores in your DI container — the static Shiny.Stores accessor self-bootstraps on first use:

    builder.Services.AddShinyStores();
    var host = builder.Build();
  3. For Blazor WebAssembly, also install and register the web stores. Because LocalStorageKeyValueStore needs IJSRuntime from the built provider, Blazor apps must call host.Services.UseShinyStores() after Build() to snapshot the resolved store into the static accessor:

    Terminal window
    dotnet add package Shiny.Extensions.Stores.Web
    builder.Services.AddLocalStorageKeyValueStore();
    var host = builder.Build();
    host.Services.UseShinyStores(); // required for Blazor

Each platform provides different store implementations, registered as keyed singletons in DI using StoreKeys:

PlatformKeyImplementation
AndroidStoreKeys.DefaultSharedPreferences
AndroidStoreKeys.SecureEncryptedSharedPreferences
iOS / macOSStoreKeys.DefaultNSUserDefaults
iOS / macOSStoreKeys.SecureKeychain
WindowsStoreKeys.DefaultApplicationData.LocalSettings
WindowsStoreKeys.SecureSecure Storage
Blazor WebAssemblyStoreKeys.DefaultlocalStorage
Blazor WebAssembly"session"sessionStorage

The simplest path is the static Shiny.Stores accessor:

public class SettingsService
{
public void SaveTheme(string theme) => Shiny.Stores.Default.Set("theme", theme);
public string? GetTheme() => Shiny.Stores.Default.Get<string>("theme");
public void SaveToken(string token) => Shiny.Stores.Secure.Set("auth_token", token);
}

The accessor is self-bootstrapping on mobile/desktop — Default and Secure lazily construct the platform-native store on first access, so no post-build initialization is required. For Blazor (where IJSRuntime only exists post-build) call host.Services.UseShinyStores() after host.Build() to snapshot the DI-resolved store into the static accessor. For tests and custom keys, use Shiny.Stores.Register(key, store) to override or register an arbitrary IKeyValueStore, and Shiny.Stores.Reset() to clear between runs.

For DI-friendly access, inject the keyed IKeyValueStore directly:

public class SettingsService(
[FromKeyedServices(StoreKeys.Default)] IKeyValueStore settings,
[FromKeyedServices(StoreKeys.Secure)] IKeyValueStore secure
)
{
public void SaveTheme(string theme) => settings.Set("theme", theme);
public string GetTheme() => settings.Get<string>("theme") ?? "light";
}
store.Get<T>(key, defaultValue); // Get with a default fallback
store.GetRequired<T>(key); // Throws if key is not found
store.SetOrRemove(key, value); // Removes the key if value is null
store.SetDefault<T>(key, value); // Only sets if the key doesn't already exist
store.IncrementValue(key); // Thread-safe integer increment

Implement IKeyValueStore to create your own storage backend, then register it as a keyed singleton:

public class RedisKeyValueStore : IKeyValueStore
{
public bool IsReadOnly => false;
public T? Get<T>(string key) { /* ... */ }
public void Set<T>(string key, T value) { /* ... */ }
public bool Contains(string key) { /* ... */ }
public bool Remove(string key) { /* ... */ }
public void Clear() { /* ... */ }
}
builder.Services.AddKeyedSingleton<IKeyValueStore, RedisKeyValueStore>("redis");
// DI lookup
[FromKeyedServices("redis")] IKeyValueStore redis;
// To make a custom key visible to the static helper, register it explicitly.
// (UseShinyStores only snapshots StoreKeys.Default / StoreKeys.Secure.)
Shiny.Stores.Register("redis", host.Services.GetRequiredKeyedService<IKeyValueStore>("redis"));
Shiny.Stores.Keyed("redis").Set("key", "value");
claude plugin marketplace add shinyorg/skills
claude plugin install shiny-extensions@shiny
copilot plugin marketplace add https://github.com/shinyorg/skills
copilot plugin install shiny-extensions@shiny
View shiny-extensions Plugin