loading

Check support

IsSupported

Returns true when the runtime exposes navigator.permissions. All evergreen browsers support it, but individual descriptor names still vary per engine.

C#
@inject Bit.Butil.Permissions permissions

var isSupported = await permissions.IsSupported();
Live sample
support output
Results will appear here when you interact with the samples.

Query a permission

Query

Returns the current PermissionState for a descriptor name: Granted, Denied or Prompt. Querying never shows a prompt - it only reads the stored decision. Descriptor names the engine does not recognize come back as Unknown instead of throwing.

C#
var state = await permissions.Query("geolocation");

if (state is PermissionState.Granted)
{
    // safe to call the API without a prompt appearing
}
Live sample
Permission name
query output
Results will appear here when you interact with the samples.

Snapshot all common descriptors

Query

Because Query is cheap and prompt-free, you can sweep a list of descriptors at startup to build a capability snapshot - handy for showing settings toggles in the right initial state.

C#
string[] names = ["geolocation", "notifications", "camera", "microphone", "clipboard-read", "clipboard-write"];

foreach (var name in names)
{
    var state = await permissions.Query(name);
    // Granted / Denied / Prompt / Unknown
}
Live sample
snapshot output
Results will appear here when you interact with the samples.

Watch a permission for changes

SubscribeChange

A permission can change without your page doing anything - the user revokes camera access from the address bar, or grants notifications in site settings. SubscribeChange is the only way to notice that short of polling Query. It returns the state at subscription time alongside the subscription, so you don't have to call Query first and race the handler. An unrecognized descriptor yields Unknown with a null subscription - there is nothing to watch.

Razor
@implements IAsyncDisposable
@inject Bit.Butil.Permissions permissions

@code {
    private ButilSubscription? _subscription;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender is false) return;

        var (state, subscription) = await permissions.SubscribeChange("geolocation", newState =>
        {
            // the user granted or revoked it from browser UI
            InvokeAsync(StateHasChanged);
        });

        if (subscription is null)
        {
            // no Permissions API, or this browser doesn't know the descriptor
            return;
        }

        _subscription = subscription;
    }

    public async ValueTask DisposeAsync()
    {
        if (_subscription is not null) await _subscription.DisposeAsync();
    }
}
Live sample
change output
Results will appear here when you interact with the samples.
Note:
Descriptor names vary by engine Chromium understands the widest set (camera, microphone, clipboard-read, background-sync, ...), while Firefox and Safari accept fewer names. An unrecognized name is not an error - Butil maps it to PermissionState.Unknown so a cross-browser sweep never throws.
Note:
Querying is passive The Permissions API reads state; it never asks the user for anything. To actually obtain a permission, call the owning API - for example Notification.RequestPermission or Geolocation.GetCurrentPosition - which triggers the browser's own prompt.

API reference

Member
Signature
Description
IsSupported
ValueTask<bool> IsSupported()
True when the runtime exposes navigator.permissions.
Query
Task<PermissionState> Query(string name)
Returns the current state for a given permission descriptor name (granted, denied, prompt); unknown names return PermissionState.Unknown.
SubscribeChange
Task<(PermissionState State, ButilSubscription? Subscription)> SubscribeChange(string name, Action<PermissionState> handler)
Watches a permission and calls the handler whenever its state changes - including revocations made from browser UI. Returns the state at subscription time; the subscription is null for an unsupported runtime or descriptor.
PermissionState
enum PermissionState { Granted, Denied, Prompt, Unknown }
Mirrors PermissionStatus.state, plus Unknown for unsupported descriptors or runtimes.
An unhandled error has occurred. Reload 🗙