UI Extensions
Where protocol plugins handle a wire protocol (gRPC, REST, Kafka, …), UI extensions handle a payload semantic. The two layers are deliberately separate: a protocol plugin carries opaque bytes; an extension recognises what those bytes mean and mounts a matching widget — a map for coordinate.wgs84 fields, a chart for time-series, an audio player for audio.opus, and so on.
Both ship as NuGet packages and install through the same paths. The split exists so users who never touch a map don't pay for ~870 KB of WebGL JS on every workbench load.
Available extensions
| Extension | Package | What it does |
|---|---|---|
| MapLibre | Kuestenlogik.Bowire.Map |
Renders fields tagged with coordinate.wgs84 (latitude/longitude pairs, GeoJSON geometries) as a MapLibre GL JS map. Read-only viewer + drag-to-edit pin for request fields. Bundles MapLibre + a default basemap. |
More extensions are planned (image viewer, audio player, time-series chart, JSON-tree differ) — each one targeting a different semantic kind from the frame-semantics framework.
Installing an extension
Standalone CLI
Same bowire plugin install subcommand the protocol plugins use:
bowire plugin install Kuestenlogik.Bowire.Map
The package lands in ~/.bowire/plugins/. On the next workbench load, Bowire registers the extension with the UI extension framework; the map widget mounts automatically the next time a payload with a matching semantic shows up.
To remove it, drop in bowire plugin uninstall Kuestenlogik.Bowire.Map. To see which extensions are installed, bowire plugin list covers both protocol plugins and extensions (the PackageType column distinguishes BowirePlugin from BowireExtension).
Embedded ASP.NET
Same dotnet add package path the protocol plugins use:
dotnet add package Kuestenlogik.Bowire.Map
No host-side wiring needed beyond AddBowire() + MapBowire() — the extension assembly is picked up by the same plugin scanner and its registration runs on the first workbench request.
Docker
The container image ships the same bowire plugin install subcommand. Use the host's ~/.bowire volume-mount so the install persists across container restarts:
docker run --rm -v ~/.bowire:/home/app/.bowire \
ghcr.io/kuestenlogik/bowire:latest \
plugin install Kuestenlogik.Bowire.Map
How activation works
Extensions don't replace protocol plugins — they sit alongside them and react to semantic annotations on message fields. Annotations are produced by:
- Auto-detection at discovery time (the proto detector recognises
google.type.LatLng, the OpenAPI detector recognisesformat: lat-long, etc.). - Manual override — users can annotate any field through the Semantics picker in the sidebar.
Once a field carries the coordinate.wgs84 annotation, every payload that visits that field surfaces a map tab alongside the JSON / form views. There's no per-method config to write — the extension subscribes to the semantic kind globally and Bowire routes matching payloads to it.
See frame-semantics-framework for the detector → annotation → viewer pipeline and the extension-author contract (window.BowireExtensions.register({...})).
Configuration
The MapLibre extension picks up two flags on the standalone CLI:
| Flag | Purpose |
|---|---|
--map-basemap=<key> |
Switch the basemap: osm (default), satellite, demotiles, or a raw tile-URL template. |
--no-browser |
Skips the auto-open browser tab — same as for the workbench itself. |
In embedded mode, the corresponding BowireOptions.MapBasemap property controls the same value:
app.MapBowire(options =>
{
options.MapBasemap = "satellite";
});
Writing your own extension
The widget contract lives entirely in JS:
window.BowireExtensions.register({
id: 'com.example.heatmap',
kind: 'time-series.heatmap',
bowireApi: '1.x',
mount: (host, ctx) => { /* draw your widget */ },
unmount: (host) => { /* tear it down */ }
});
The .NET side is a thin shell: a NuGet project with PackageType=BowireExtension, an embedded JS bundle, and a discovery descriptor pointing at wwwroot/js/widgets/<bundle>.js. Bowire serves the bundle from /api/ui/extensions/<id>/<bundle> and the workbench loads it on first use.
For the full contract — capability negotiation, the ctx surface (frames$, selection$, theme, viewport, host), the registration semantics, and what changes between API majors — see frame-semantics-framework, Extension framework section.
Related
- Plugin system — the protocol-plugin layer extensions sit alongside.
- frame-semantics-framework — the architecture for both detection and consumption.