Building a custom widget
Widgets render sheet cells. SheetBot ships with text, code, image, progress, metric, task, and other widgets, and automatically loads any widget-*.js file from its static directories.
In this tutorial you build a small hello widget, register it, and use it for a sheet column.
1. Create the widget
Create static/widget-hello.js:
import { html, css, LitElement } from "https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js";
export class HelloWidget extends LitElement {
static properties = {
data: { type: String },
rowkey: { type: String },
column: { type: String },
};
static styles = css`
span {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
background: #e8f4ff;
color: #0b4f8a;
font-weight: 600;
}
`;
constructor() {
super();
this.data = "";
this.rowkey = "";
this.column = "";
}
render() {
return html`<span>Hello, ${this.data || "world"}!</span>`;
}
getCopyText() {
return `Hello, ${this.data || "world"}!`;
}
getCopyHTML() {
return `<span>${this.getCopyText()}</span>`;
}
}
customElements.define("widget-hello", HelloWidget);
Rules:
- The filename must start with
widget-and end with.js. - The custom element name must be
widget-<name>, herewidget-hello. datareceives the cell value.rowkeyandcolumnare also set.getCopyText()is used for copy operations.getCopyHTML()andgetContextMenuDefinition()are optional.- If you implement
delete(), the grid can call it for destructive context-menu actions.
2. Make SheetBot discover it
SheetBot exposes all discovered widgets at /widgets.json and imports them when a sheet page loads:
curl "$SHEETBOT_BASEURL/widgets.json"
The list is cached for five minutes by default. For development, disable the cache and restart SheetBot:
export SHEETBOT_WIDGET_CACHE_TTL=0
npm start
Alternatively, keep the default and wait for the cache to expire. A hard refresh in the browser also helps after changing a widget module.
3. Use the widget for a column
Use the build-status sheet from Customizing your sheet, or any sheet with a text column. Set the column’s widgettype to hello:
sqlite3 sheets/build-status.db \
"UPDATE columnstructure SET widgettype = 'hello' WHERE name = 'name';"
Refresh http://localhost:3000/sheet.html?sheet=build-status. The name column now renders as Hello, Linux x64!, and the value is still copied as plain text.
If the column does not exist in columnstructure, add it first:
INSERT INTO columnstructure
(name, widgettype, columnorder)
VALUES
('name', 'hello', 1);
4. Debug a widget
- The widget does not appear — check that the file is in
static/(or a directory inSHEETBOT_STATIC_SEARCH_PATHS), starts withwidget-, and ends with.js. /widgets.jsondoes not list it — the cache may still hold the old list. SetSHEETBOT_WIDGET_CACHE_TTL=0, restart, or wait.- The cell is empty — open the browser console. A failed dynamic import or a mismatched
customElements.definename shows up there. - The column is missing —
columnstructurehas no row for it, or the column does not exist indata_view. - Changes do not show — hard refresh. ES modules are cached by the browser.
Next
- Widgets reference — built-in widget types and the widget contract.
- Customizing your sheet — per-column widget configuration.
- Static search paths — load widgets from outside
static/.