Widget Developer Guide
This guide defines the baseline contract for adding built-in widgets to Freeboard.
Goals
- Keep widgets display-only by default.
- Keep runtime deterministic and resilient (no widget should crash the board).
- Keep behavior responsive for desktop and
smmobile layouts.
Runtime Contract
A widget plugin must provide:
typeName(stable id used in persisted dashboards)label(user-facing name in the widget picker)fields(widget, dashboard, general)(settings schema)newInstance(settings, callback)(construct runtime instance)
Most widgets should extend ReactiveWidget:
resolveInputs()reads bindings fromsnapshotusinggetBinding(path)/getTemplate(template).onInputsChanged(inputs)updates DOM for the latest values.onResize({ width, height })adapts rendering to pane size.getPreferredRows()returns expected minimum pane height impact.
See also: Widget Runtime
Settings Schema Patterns
- Put shared title/enable fields in
general. - Group custom settings into
DisplayandBindings. - Use explicit defaults so dashboard JSON is predictable.
- Prefer simple scalar fields (
text,number,boolean,option) over free-form code.
Binding and Template Rules
- Binding paths should resolve against datasource snapshots:
<datasourceTitle>.path.to.valuedatasources.<datasourceId>.path.to.value
- If a binding cannot resolve, render a safe empty value (for example
—). - Template strings should use
\{\{ path.to.value \}\}placeholders only.
Responsive Behavior Requirements
- Handle narrow panes (
width < ~320px) inonResize. - Degrade optional UI first (legends, extra labels, dense spacing).
- Avoid clipping/overflow for core values.
- Use
overflow: autoonly when necessary (tables/lists), not as a generic fallback.
Error Handling Requirements
- Guard value parsing (
Number(...), JSON parse) and use safe fallbacks. - Avoid throwing in
onInputsChanged; prefer empty-state rendering. - Clean up timers/animation frames in
onDispose.
Security Requirements
- No
eval,Function, dynamic script execution, or equivalent code generation. - No direct network requests in widget runtime.
- No unsafe HTML injection paths without strict sanitization.
- No direct writes to browser storage APIs from widget implementations.
- Respect execution-mode boundaries; do not bypass trusted/safe runtime controls.
Testing Standards
Widget changes should include:
- Unit tests for parsing/normalization helpers.
- Runtime rendering tests for empty + valid states.
- Responsive tests for narrow layout behavior.
- Registration coverage (widget appears in plugin registry list).
Example Widget Skeleton
js
import { ReactiveWidget } from "./runtime/ReactiveWidget.js";
export class ValueBadgeWidget extends ReactiveWidget {
static typeName = "value-badge";
static label = "Value Badge";
static preferredRows = 2;
static fields = (widget, dashboard, general) => [
general,
{
label: "Bindings",
icon: "hi-variable",
name: "bindings",
settings: {
valuePath: widget?.settings?.valuePath,
},
fields: [{ name: "valuePath", label: "Value Path", type: "text", required: true }],
},
];
static newInstance(settings, callback) {
callback(new ValueBadgeWidget(settings));
}
constructor(settings) {
super(settings);
this.badge = document.createElement("div");
this.badge.style.padding = "6px 10px";
this.badge.style.border = "1px solid var(--color-shade-3)";
this.widgetElement.append(this.badge);
}
resolveInputs() {
return {
value: this.getBinding(this.currentSettings?.valuePath),
};
}
onInputsChanged(inputs) {
this.badge.textContent =
inputs.value === null || inputs.value === undefined || inputs.value === ""
? "—"
: String(inputs.value);
}
}