loading
Warning:
Installed apps only A launch is delivered to an installed app whose manifest declares what it handles. A page open in a browser tab never receives one, so on this site the consumer will simply never fire - install the sample app and open a file with it to see the real thing.
Note:
Register the consumer early The browser delivers the launch the moment a consumer exists, and a page may set one only once. Butil installs its own consumer while the module is evaluated and parks the launch until your handler arrives, so registering in OnAfterRenderAsync is safe. Under UseLazyScripts, call IsSupported() early in start-up so the module is imported before the launch arrives.

Support check

IsSupported / SupportsFiles

IsSupported is true when the runtime exposes window.launchQueue. SupportsFiles is a separate question: a runtime can deliver a launch without ever putting files on it.

C#
@inject Bit.Butil.LaunchQueue launchQueue

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

Receive the launch

SetConsumer

Registers the handler that receives this app's launch, with the target URL and any files. It fires immediately when the launch has already arrived, so registering late is safe. Dispose the returned subscription in DisposeAsync.

@page "/open"
@implements IAsyncDisposable
@inject Bit.Butil.LaunchQueue launchQueue

@code {
    private string? _targetUrl;
    private LaunchFile[] _files = [];
    private ButilSubscription? _sub;

    // Safe here rather than in OnInitialized: the consumer fires immediately when the launch has
    // already arrived, so registering after the first render loses nothing.
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender is false) return;

        _sub = await launchQueue.SetConsumer(launch =>
        {
            _targetUrl = launch.TargetUrl;
            _files = launch.Files;
            InvokeAsync(StateHasChanged);
        });
    }

    public async ValueTask DisposeAsync()
    {
        if (_sub is not null) await _sub.DisposeAsync();
    }
}
Live sample
launch output
Results will appear here when you interact with the samples.

Read a launched file

ReadText / ReadBytes

A launched file's handle stays on the JS side - it cannot cross interop - so contents are read by the file's Index. Both overloads take either the LaunchFile or its index; pass the LaunchFile where an app can be launched more than once, since it carries the LaunchId that keeps it pointing at its own launch's files rather than at the newest launch's.

C#
foreach (var file in launch.Files)
{
    var text = await launchQueue.ReadText(file);
    if (text is not null) Open(file.Name, text);
}
Live sample
read output
Results will appear here when you interact with the samples.

Write back to a launched file

WriteText / WriteBytes

The 'save' half of an editor launched through file_handlers. The browser asks for the write permission on the first save; a refusal returns false rather than throwing.

C#
var saved = await launchQueue.WriteText(file, editorContents);
if (saved is false)
{
    // the user refused the write permission, or the handle went stale
}
Live sample
Contents to write
write output
Results will appear here when you interact with the samples.

The manifest side

file_handlers / protocol_handlers / launch_handler

None of this fires without the manifest declaring what the app handles. file_handlers registers the app for a file type, protocol_handlers for a URL scheme, and launch_handler decides whether a second launch opens a new window or reuses the existing one.

JSON
{
  "file_handlers": [
    { "action": "/open", "accept": { "text/markdown": [".md"] } }
  ],
  "protocol_handlers": [
    { "protocol": "web+butil", "url": "/open?link=%s" }
  ],
  "launch_handler": { "client_mode": "focus-existing" }
}

API reference

Member
Signature
Description
IsSupported
ValueTask<bool> IsSupported()
True when the runtime exposes window.launchQueue (Chromium only).
SupportsFiles
ValueTask<bool> SupportsFiles()
True when the runtime also implements File Handling, i.e. a launch can carry files.
SetConsumer
Task<ButilSubscription> SetConsumer(Action<LaunchParams> handler)
Registers the handler that receives this app's launch. Fires immediately when the launch has already arrived.
ReadText
ValueTask<string?> ReadText(LaunchFile file) / ReadText(int index)
Reads a launched file as text. Null when the handle went stale or access was refused.
ReadBytes
ValueTask<byte[]?> ReadBytes(LaunchFile file) / ReadBytes(int index)
Reads a launched file as bytes. Null when the handle went stale or access was refused.
WriteText
ValueTask<bool> WriteText(LaunchFile file, string contents) / WriteText(int index, string contents)
Writes text back to a launched file. False when the runtime can't write, or the write permission was refused.
WriteBytes
ValueTask<bool> WriteBytes(LaunchFile file, byte[] contents) / WriteBytes(int index, byte[] contents)
Writes bytes back to a launched file.
DisposeAsync
ValueTask DisposeAsync()
Unregisters every handler registered through this instance and releases its interop reference.
An unhandled error has occurred. Reload 🗙