CookieStore
The modern, fully asynchronous cookie API. Unlike the legacy document.cookie string, CookieStore returns complete cookie metadata - domain, path, expiry, SameSite - as structured objects.
@inject Bit.Butil.CookieStore cookieStoreMDN reference
CookieStore is not available in every browser, so feature-detect before relying on it. When it is missing, fall back to the legacy Cookie service - it works everywhere but only exposes name/value pairs.
@inject Bit.Butil.CookieStore cookieStore
if (await cookieStore.IsSupported())
{
// safe to use cookieStore
}Set accepts a CookieStoreItem carrying the full attribute set. Omitted attributes get browser defaults (Path becomes /, SameSite becomes strict in Chromium). Leave Expires null for a session cookie, or set an absolute expiry.
await cookieStore.Set(new CookieStoreItem
{
Name = "butil-docs",
Value = "hello",
SameSite = "lax",
Expires = DateTimeOffset.UtcNow.AddDays(1),
});This is where CookieStore earns its keep: Get and GetAll return CookieStoreItem objects with every attribute the browser knows - domain, path, expiry, secure flag, SameSite policy and partitioned state - not just the name and value.
var one = await cookieStore.Get("butil-docs"); // CookieStoreItem? - null when absent
var all = await cookieStore.GetAll(); // CookieStoreItem[]
foreach (var c in all)
{
// c.Name, c.Value, c.Domain, c.Path, c.Expires, c.Secure, c.SameSite, c.Partitioned
}Delete removes the named cookie directly - no MaxAge = 0 workaround needed. Deleting a cookie that does not exist is a no-op.
await cookieStore.Delete("butil-docs");The change event is the one thing document.cookie can never give you. It fires for cookies written by script, by a server's Set-Cookie header, and on expiry - which makes it the way to notice a session cookie dying without polling for it. Each notification carries both halves: cookies written and cookies removed. An overwrite arrives as a delete plus a set, so both lists can be non-empty at once.
private ButilSubscription? subscription;
subscription = await cookieStore.SubscribeChange(change =>
{
foreach (var c in change.Changed) { /* written or updated */ }
foreach (var c in change.Deleted) { /* removed or expired */ }
});
if (subscription is null)
{
// no CookieStore here - document.cookie has no change event to fall back on
}
// later
await subscription.DisposeAsync();IsSupported and fall back to the
Cookie service elsewhere.
Cookie for universal support with
name/value access, and CookieStore when you need to read attributes back or prefer a
promise-based API. HttpOnly cookies remain invisible to both - that protection applies to all
script access.
API reference
ValueTask<bool> IsSupported()ValueTask<CookieStoreItem[]> GetAll()ValueTask<CookieStoreItem?> Get(string name)ValueTask Set(CookieStoreItem cookie)ValueTask Delete(string name)ValueTask<ButilSubscription?> SubscribeChange(Action<CookieStoreChange> handler)class CookieStoreItem { string Name; string Value; string? Domain; string? Path; DateTimeOffset? Expires; bool Secure; string? SameSite; bool? Partitioned; }class CookieStoreChange { CookieStoreItem[] Changed; CookieStoreItem[] Deleted; }