loading

Could not find 'BitButil.something' in 'window'

The bridge script has not loaded. Either the script tag is missing from the host page, or it comes after the Blazor script and the app booted before window.BitButil existed. Put it first. If it is already first and still failing, check the network tab: a 404 on the _content path means static web assets are not being served, which usually means the host project does not reference Bit.Butil (a project reference from the client alone is not enough when a separate project does the hosting). In an app using lazy scripts there is deliberately no script tag and this error means something else - see the import failure below.

HTML
<!-- correct order -->
<script src="_content/Bit.Butil/bit-butil.js"></script>
<script src="_framework/blazor.web.js"></script>

Failed to fetch dynamically imported module

Only lazy scripts can produce this: the app called an API, the library asked the browser to import that API's module, and the file was not there. Check the failing URL in the network tab. A 404 under _content/Bit.Butil/modules/ in a published app means the module files were not published - the default publish keeps the bundle and drops them, so turning lazy scripts on from C# alone needs BitButilIncludeScriptModules in the csproj as well, which is why the BitButilLazyScripts property is the better switch: it does both. A trimmed publish narrows the modules further - it publishes only the ones the trimmed assembly can still import, which is every one the library asks for on its own, so a module missing there means something outside those calls wanted it: set BitButilIncludeScriptModules to publish all of them. A URL pointing somewhere else entirely means ScriptModulesPath was set to a path the assets are not served from. And in a Blazor Web App the property has to be set in the server project as well as the client, or the prerender pass runs in a project that never turned lazy scripts on.

XML
<!-- the switch that both loads and publishes the modules -->
<PropertyGroup>
  <BitButilLazyScripts>true</BitButilLazyScripts>
</PropertyGroup>

Cannot provide a value for property 'clipboard'

AddBitButilServices was never called, or it was called in only one of the two containers your app has. A prerendering app instantiates every component twice - once on the server for the prerender pass and once in the browser - so both service collections have to register the same set, or prerendering fails with a missing-service exception before the browser ever sees the page. If this only happens in a published build, the cause is trimming instead: registration discovers services by reflection, so a Butil class reached only through reflection - never through an injection point, a field or a parameter anywhere in your code - is removed from the bundle and is not there to register.

C#
// Client/Program.cs
builder.Services.AddBitButilServices();

// Server/Program.cs - the prerender host needs it too
builder.Services.AddBitButilServices();

The value is empty on the first render, correct afterwards

This is prerendering working as designed. There is no browser during the server pass, so Butil returns a safe default - an empty string, an empty array, false, zero - rather than throwing. Reading in OnInitializedAsync therefore gets you the default, and reading again in the browser gets the real value. Move any read whose answer you branch on into OnAfterRenderAsync guarded by firstRender, and call StateHasChanged afterwards because the first render has already happened.

Razor
@inject Bit.Butil.LocalStorage localStorage

<p>@_token</p>

@code {
    private string? _token;

    // Not OnInitialized: that runs on the server during the prerender pass, where there is no
    // browser to ask. Butil answers with the default rather than throwing, which is why the value
    // is empty rather than the page being broken.
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender is false) return;

        _token = await localStorage.GetItem("token");

        StateHasChanged();
    }
}

An event fires, the log shows it, but the UI does not update

The callback arrived from JavaScript rather than from Blazor's own event dispatch, so nothing told the renderer that state changed. Mutate your state in the handler and then ask for a re-render through InvokeAsync(StateHasChanged) - InvokeAsync, not StateHasChanged directly, because the callback may not be running on the renderer's synchronization context.

C#
await box.SubscribeEvent<ButilMouseEventArgs>(js, ButilEvents.Click, args =>
{
    _clicks++;
    InvokeAsync(StateHasChanged);
});

NotAllowedError, or a call that silently does nothing

Three separate rules produce this, and the message rarely says which. The page may not be a secure context, so the API object does not exist at all. The user may have denied the permission, in which case it stays denied until they clear it in site settings. Or the call may have lost its user gesture - the window closes as soon as the handler yields to something slow, so a permission-gated call has to be the first await in a click handler, not the one after a server round trip.

Razor
@inject Bit.Butil.Window window
@inject Bit.Butil.Permissions permissions

@code {
    // Check which of the three it is before guessing:
    private async Task Diagnose()
    {
        var secure = await window.IsSecureContext();            // false → no API at all
        var state = await permissions.Query("clipboard-write"); // Denied → user said no
        // still failing → the gesture was gone by the time you called
    }
}

JSDisconnectedException while navigating away

On Blazor Server the circuit can vanish before your teardown finishes, and an interop call in DisposeAsync then throws. Butil's own disposal paths already swallow the three exceptions that teardown legitimately produces - JSDisconnectedException, OperationCanceledException and ObjectDisposedException - so disposing a ButilSubscription or an AnimationHandle is safe. Your own interop in DisposeAsync is not covered by that and needs the same guard.

Razor
@implements IAsyncDisposable
@inject IJSRuntime js
@inject Bit.Butil.Keyboard keyboard

@code {
    private ButilSubscription? subscription;

    public async ValueTask DisposeAsync()
    {
        try
        {
            if (subscription is not null) await subscription.DisposeAsync();   // already safe
            await js.InvokeVoidAsync("myOwnCleanup");                          // yours is not
        }
        catch (JSDisconnectedException) { }
        catch (OperationCanceledException) { }
        catch (ObjectDisposedException) { }
    }
}

Handlers keep firing after the component is gone

A subscription that is never disposed keeps the JavaScript side holding a reference to your component's delegate, so it fires against a component that is no longer rendered - and the component cannot be collected. Every Butil subscription hands back a token for exactly this reason. Keep it, and dispose it. Watch out for the bulk removers too: Keyboard.RemoveAll clears every shortcut registered against that Keyboard instance, not only the ones your component added.

Razor
@implements IAsyncDisposable

@code {
    private ButilSubscription? sub;

    public async ValueTask DisposeAsync()
    {
        if (sub is not null) await sub.DisposeAsync();
    }
}

It works on localhost and breaks in production

Browsers treat localhost as a secure context so that development works over plain HTTP. The moment the app is served from a real host over HTTP, every secure-context API disappears - clipboard, crypto, geolocation, service workers, web locks, storage estimates and more. Serve over HTTPS. The browser-support page lists exactly which APIs are affected, and IsSupported() answers it per API: it probes for the object itself, which an insecure context does not have, so it reports false for exactly the reason you are chasing.

Razor
@inject Bit.Butil.Window window
@inject Bit.Butil.Clipboard clipboard
@inject Bit.Butil.Crypto crypto

@code {
    private async Task Check()
    {
        if (await window.IsSecureContext() is false)
        {
            // this is the difference between your machine and production
        }

        // per-API, and the same answer: false here means navigator.clipboard
        // does not exist, which over plain HTTP it never does
        if (await clipboard.IsSupported() is false) { /* ... */ }
        if (await crypto.IsSupported() is false) { /* SubtleCrypto is https-only */ }
    }
}

One API stops working after publish, the rest are fine

A published app rebuilds bit-butil.js from the modules it can still reach, so the JavaScript for an API that neither the trimmer nor BitButilScriptScan could see is genuinely not in the bundle - and the symptom is the missing-bridge error above, for that one API. It is almost always correct: the API really is only reached from code that was trimmed away, or only through reflection, which neither can follow. Name the module - or the Bit.Butil class behind it - in BitButilScriptModule, which is added to whatever they concluded; or set BitButilTrimScripts to false to publish the whole bundle and confirm that is the cause first.

XML
<!-- Keep this one whatever the publish concluded -->
<ItemGroup>
  <BitButilScriptModule Include="Clipboard" />
</ItemGroup>

<!-- Or rule trimming out as the cause entirely -->
<PropertyGroup>
  <BitButilTrimScripts>false</BitButilTrimScripts>
</PropertyGroup>

I set the trimming properties and the bundle is still the full one

Start with which project BitButilTrimScripts is on in. It only ever trims in the one that publishes the app's static web assets - the WebAssembly head, or a Blazor Web App's server project - and a shared Directory.Build.props hands it to every Razor class library and every MAUI/Blazor Hybrid head as well, none of which publish anything. That is harmless: the switch is fine to share, and the scan it brings with it is read per project, so only the head acts on it. What is not harmless is the switch being nowhere near the head - it defaults to on in a WebAssembly project and off everywhere else, so a Blazor Server app or a Web App's server project has to turn it on. If it is on in the right project, look for a Bit.Butil message in the build output saying the trimming stood down, which means a scan or a BitButilScriptModule list was written out somewhere that is not the head. Two other explanations: BitButilScriptScan is set to None, which leaves the switch on with nothing to trim against on an untrimmed publish - or you are looking at a build. A build always keeps the full bundle, dotnet run and dotnet watch included, so the comparison to make is between two publishes.

XML
<!-- src/Directory.Build.props - the switch is fine to share, and brings the scan with it -->
<PropertyGroup>
  <BitButilTrimScripts>true</BitButilTrimScripts>
</PropertyGroup>

<!-- MyApp.Server.csproj - but what the app CALLS belongs to the head that publishes it -->
<ItemGroup>
  <BitButilScriptModule Include="WebAuthn" />
</ItemGroup>

It works in debug and breaks after publish

Trimming removed a type that only the JSON serializer ever referenced. Butil's own types are annotated and survive, but your models crossing the interop boundary - anything you serialize into a Butil call or deserialize out of one - need to be reachable by the trimmer. Either keep them in a project that is not trimmed, or annotate them so the linker keeps their members.

C#
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public class MyPayload
{
    public string Name { get; set; } = "";
}
Note:
Anything to do with a published bundle The three sections above about trimming are the symptoms; JavaScript trimming is the feature they come from - the switch, the three signals it works from, where the properties go, and a live check that reads back which modules the app you are looking at downloaded.
Note:
Narrowing it down quickly Most reports resolve to one of three questions. Is the bridge loaded - does window.BitButil exist in the DevTools console (under lazy scripts it appears only after the first call, and Object.keys(window.BitButil) is then the list of modules imported so far)? Is there a browser yet - is the code running after the first render? Is the API even there - what does IsSupported() say? The browser-support page answers the third one live for whichever browser you are testing in.
Warning:
Still stuck? Open an issue on the bitplatform repository with the render mode, the .NET version, the browser, and the exact exception. The render mode and the browser between them explain most of what looks inexplicable.
An unhandled error has occurred. Reload 🗙