loading
Warning:
Chromium only Only Chromium fires beforeinstallprompt. On Safari and Firefox installing is a manual browser-menu action, so IsSupported is false and there is nothing to prompt - treat the install button as an enhancement, not a feature you can rely on.
Note:
The event fires once, early Butil attaches its listener while the installPrompt module is evaluated, which with the classic bundle is early enough. Under UseLazyScripts the module is imported on first use - possibly after the event has already fired - so add this to your host page and Butil will adopt what it stashed:
HTML
<script>
  window.addEventListener('beforeinstallprompt', e => {
    e.preventDefault();
    window.BitButilDeferredInstallPrompt = e;
  });
</script>

Support check

IsSupported

True when the runtime fires beforeinstallprompt at all. During prerender/SSR the check returns false rather than throwing, so defer it to OnAfterRenderAsync.

C#
@inject Bit.Butil.InstallPrompt installPrompt

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

Is the app already installed?

IsStandalone / WasInstalled

IsStandalone is the portable check - it is true whenever the page is running as an installed app rather than in a tab, covering the standalone, minimal-ui, fullscreen and window-controls-overlay display modes plus iOS Safari's own flag. WasInstalled only reports an install that happened during this page's lifetime.

C#
if (await installPrompt.IsStandalone())
{
    // running as an installed app - hide the install button
}
Live sample
install state output
Results will appear here when you interact with the samples.

Can we prompt right now?

IsAvailable / GetPlatforms

IsAvailable is the condition to show your install button on: true only once the browser has decided the app is installable and before the deferred prompt has been used. GetPlatforms reports what the captured event offers - ['web'], or ['play', 'web'] when the manifest names a related native app.

C#
_canInstall = await installPrompt.IsAvailable();
var platforms = await installPrompt.GetPlatforms();
Live sample
availability output
Results will appear here when you interact with the samples.

Show the install dialog

Prompt

Shows the deferred dialog and resolves with what the user chose. Must be called from a user gesture. The event is spent afterwards whatever the answer, so IsAvailable becomes false; a user who dismissed it can only be asked again after the browser fires beforeinstallprompt anew on a later visit.

@inject Bit.Butil.InstallPrompt installPrompt

@* Must be a real user gesture: a prompt shown from a timer or from OnInitialized is refused. *@
<button @onclick="Install" disabled="@(_canInstall is false)">Install</button>

@code {
    private bool _canInstall;

    private async Task Install()
    {
        var result = await installPrompt.Prompt();

        if (result.Accepted)
        {
            // installing on result.Platform
        }
        else if (result.Outcome == InstallPromptOutcome.Dismissed)
        {
            // asked and declined - don't nag
        }

        // The event is spent whatever the answer.
        _canInstall = false;
    }
}
Live sample
prompt output
Results will appear here when you interact with the samples.

React to availability and installation

OnAvailable / OnInstalled

OnAvailable fires when a prompt becomes offerable - and immediately if one is already in hand, so subscribing late is safe. OnInstalled fires after the browser has installed the app: the moment to hide the button. Both return a ButilSubscription; dispose it in DisposeAsync.

C#
_availableSub = await installPrompt.OnAvailable(platforms =>
{
    _canInstall = true;
    InvokeAsync(StateHasChanged);
});

_installedSub = await installPrompt.OnInstalled(() =>
{
    _canInstall = false;
    InvokeAsync(StateHasChanged);
});
Live sample
event output
Results will appear here when you interact with the samples.

API reference

Member
Signature
Description
IsSupported
ValueTask<bool> IsSupported()
True when the runtime fires beforeinstallprompt at all (Chromium only).
IsAvailable
ValueTask<bool> IsAvailable()
True when a deferred prompt is in hand, i.e. Prompt() would show the dialog right now.
GetPlatforms
ValueTask<string[]> GetPlatforms()
The platforms the captured event offers an install on. Empty when no prompt is available.
WasInstalled
ValueTask<bool> WasInstalled()
True when appinstalled has fired during this page's lifetime. Per page load, not persistent.
IsStandalone
ValueTask<bool> IsStandalone()
True when the page is running as an installed app rather than in a browser tab.
Prompt
ValueTask<InstallPromptResult> Prompt()
Shows the install dialog and resolves once the user has answered. Requires a user gesture; the deferred event is spent afterwards.
OnAvailable
Task<ButilSubscription> OnAvailable(Action<string[]> handler)
Calls the handler when an install prompt becomes available, with the platforms it offers. Fires immediately when one is already in hand.
OnInstalled
Task<ButilSubscription> OnInstalled(Action handler)
Calls the handler once the app has been installed.
DisposeAsync
ValueTask DisposeAsync()
Detaches every listener registered through this instance and releases its interop reference.
An unhandled error has occurred. Reload 🗙