Viewer view plugins
The Building Blocks Viewer renders a few content-based visualizations out of the box — a map view for GeoJSON, a 3D view, and a web view for HTML/XML content. View plugins let a register add its own custom visualizations for example snippets or transform outputs, without that code living inside the viewer itself.
A view plugin is a small client-side ES module the viewer loads at runtime (via a browser-native
import()) and matches against the content of an example snippet or transform output. If it
matches, the viewer adds an extra tab that renders the plugin’s own view alongside the built-in
code/schema tabs.
Adding a view plugin to a register
Declare one or more plugins under viewer.view-plugins in bblocks-config.yaml:
viewer:
view-plugins:
- url: https://example.org/my-plugin/dist/index.js
export: MyPlugin # optional; a single name, an array of names, or omitted for the default export
weight: 100 # optional; higher sorts earlier among plugin tabs
url— the plugin’s ES module, fetched at runtime viaimport(). Must be served with permissive CORS (GitHub Pages does this by default).export— which export(s) of the module to use as plugin classes. Omit it (or set it tonull/""/[]) to use the module’s default export; set it to an array to pull in several plugin classes from the same file/bundle.weight— controls tab ordering relative to other view plugins (higher sorts earlier). The viewer’s own built-in views (map/3D/web) always sort first regardless ofweight.
This is written through to register.json (as viewer.viewPlugins, camelCased) and read by the
viewer at runtime — no postprocessing step touches the plugin code itself, so a register only ever
needs to point at where the plugin is hosted.
Out-of-the-box plugins
The viewer’s built-in map, 3D, and web views are themselves ordinary view plugins, published as bblocks-viewer-base-plugins:
- Map view (
GeoJsonMapPlugin) — renders GeoJSONFeature/FeatureCollectioncontent on a Leaflet map, with semantically-enriched popups for JSON-LD-aware content. - 3D view (
ThreeDPlugin) — renders GeoJSON with Z coordinates, or CityJSON-like topologies, as an interactive 3D scene (orbit controls, grid/wireframe/edges/vertices toggles). - Web view (
WebViewPlugin) — points a sandboxed iframe at HTML content served from an absolute URL.
These ship with the viewer automatically, so a register doesn’t need to declare them. The package is also usable as a self-hosted plugin bundle in its own right — for example, if you want one of these same visualizations available for a custom media type it doesn’t match by default — and is a good real-world reference for writing your own plugin, beyond the starter template’s examples.
Writing a view plugin
Start from the bblocks-view-plugin-starter template repo — click “Use this template” on GitHub, or clone it:
git clone https://github.com/ogcincubator/bblocks-view-plugin-starter my-plugin
cd my-plugin
npm install
It contains a minimal plugin skeleton (in both TypeScript and JavaScript — pick one, delete the
other), two complete worked examples (a JSON tree view and a CSV table view), and a build setup
producing a single deployable dist/index.js.
Plugin interface
A plugin module exports one or more classes (default export, or named exports referenced by
export in bblocks-config.yaml). Each class:
class MyPlugin {
// MIME types this plugin might handle, checked against each candidate's resolved MIME type
// before the class is instantiated. Supports wildcards ('text/*', '*/*'). Required.
static supportedTypes = ['application/geo+json'];
// Tab label and icon (MDI name, e.g. 'mdi-map') — optional, both fall back to a generic default.
static viewName = 'Map';
static icon = 'mdi-map';
// candidates: one entry per available representation of the same content — one per example
// snippet language, or a single-element array for a transform output. Each candidate is
// { type, content, url, label }; a plugin picks whichever one(s) it actually wants.
//
// context: { bblock, viewerConfig } — host information beyond the candidates themselves.
// Always supplied; using it is what's optional.
constructor(candidates, context = {}) {
this.candidates = candidates;
}
// Optional content-based filter for when the type filter alone isn't precise enough. Only
// called for candidates whose content resolved. Return false to withdraw the match — omit
// this method to always match once the type filter passes.
matches() { return true; }
// el: an empty <div> the plugin owns, sized to the viewer's plugin-tab chrome (small box with a
// fullscreen toggle). May be async.
render(el) { /* mount your view here */ }
// Optional teardown when the tab/component unmounts.
destroy(el) {}
}
A candidate has the shape:
interface ViewPluginCandidate {
type: string | null; // resolved MIME type, e.g. 'application/geo+json'
content: string | null; // resolved text, or null if not (yet) available
url: string | null; // the URL this content came from, if any
label: string; // human-readable label (snippet language, or transform id)
}
Bundle any third-party dependency your plugin needs into its own module — a plugin is a
self-contained embeddable widget, not code that shares the host viewer’s runtime. Import heavy
dependencies lazily inside render() so they’re only downloaded once the plugin actually renders,
not just because it was checked for a match.
If your plugin needs its own CSS, inject it at runtime via a <style> tag rather than relying on a
<link> in the host page — see the starter template’s README for the exact pattern (a static, not
dynamic, ?raw CSS import).
The starter’s README covers the full contract, build/bundling notes, the CSS-injection pattern, and a FAQ (local testing, error handling, shipping multiple plugins from one bundle) in more detail than reproduced here.
Trying it out
Build the plugin (npm run build), serve dist/ from any static host with permissive CORS
(GitHub Pages works out of the box), and point a register’s bblocks-config.yaml at it as shown
above. For local testing, the simplest setup is same-origin: serve the plugin file from the same
static server as the register/build directory you’re previewing in the
local viewer.
Trust model
A register only ever loads the view plugins it declares itself — plugins from imported registers
are never consulted. Declaring a plugin’s URL is equivalent to embedding a <script> on a page you
control: no sandboxing is applied, so only point at code you trust.