Roam Research Docs · Developer documentation
extension.js must default-export an object with onload and onunload functions
onload({extensionAPI, extension}) runs when the extension is enabled, and again on every app load while it stays enabled
extensionAPI — the API documented on this page, scoped to your extension; it is only passed here, not available on window
extension.version (string) — the installed version of your extension
onload may return a cleanup function — it is called at unload, right before onunload
onload works too — Roam awaits a returned Promise (logging any error), and if it resolves to a function, that function is the cleanup
onunload() runs when the extension is disabled, uninstalled, updated, or reloaded
extension.css, it is injected automatically on load and removed on unload
onunload):
extensionAPI.ui.commandPalette and extensionAPI.ui.slashCommand
extensionAPI.ai #experimental
extension.css
onunload
export default {
onload: ({ extensionAPI, extension }) => {
console.log("loading version", extension.version);
extensionAPI.ui.commandPalette.addCommand({
label: "My Extension: Say hi",
callback: () => console.log("hi")});
// optionally return a cleanup function;
// it runs at unload, right before onunload
},
onunload: () => {
// undo anything not covered by automatic cleanup
// (DOM nodes, event listeners, intervals, ...)
}
};
extensionAPI.settings
extensionAPI.settings.canSet (boolean) — false when the extension was installed "for everyone" by the graph admin and the current user is not an admin; then set only logs a warning and changes nothing
extensionAPI.settings.get
key (string, required) — the setting to read
extensionAPI.settings.get("api-key")
// => "sk-abc123"
extensionAPI.settings.set
key (string, required) — non-empty, cannot contain ".", "#", "$", "[", or "]"
value (required) — any JSON-serializable value
key is invalid; if the user cannot write settings (canSet is false), it only logs a warning
await extensionAPI.settings.set("threshold", 5)
// => null
extensionAPI.settings.getAll
extensionAPI.settings.getAll()
// =>
// {"api-key": "sk-abc123",
// "threshold": 5,
// "sync-enabled": true}
extensionAPI.settings.panel.create
id — read it back with extensionAPI.settings.get; button and reactComponent rows save nothing themselves
config (map, required)
tabTitle (string) — the title of your extension's tab in Settings
settings (array of maps) — one entry per settings row:
id (string, required) — the storage key for the row; non-empty, cannot contain ".", "#", "$", "[", or "]"
name (string | React element) — the row's title
description (string | React element, optional) — shown under the name
className (string, optional) — extra CSS class on the row
action (map, required) — the control, one of:
{type: "switch", onChange} — a toggle; saved on change; onChange (optional) receives the DOM event
{type: "input", placeholder, onChange} — a text field; saved on blur; onChange (optional) fires on each keystroke with the event
{type: "select", items, onChange} — a dropdown; items is an array of strings; saved on selection; onChange (optional) receives the selected item
{type: "button", content, onClick, class} — a button; content is the label, onClick (optional) the click handler, class (optional) replaces the default button styling
{type: "reactComponent", component} — renders your React component; manage state yourself with extensionAPI.settings.set/get
id is invalid
await extensionAPI.settings.panel.create({
tabTitle: "My Extension",
settings: [
{id: "sync-enabled",
name: "Sync",
description: "Sync data automatically",
action: {type: "switch",
onChange: (evt) => console.log("switch:", evt.target.checked)}},
{id: "api-key",
name: "API key",
action: {type: "input",
placeholder: "paste your key",
onChange: (evt) => console.log("typing:", evt.target.value)}},
{id: "mode",
name: "Mode",
action: {type: "select",
items: ["fast", "thorough"],
onChange: (item) => console.log("selected:", item)}},
{id: "reset",
name: "Reset",
action: {type: "button",
content: "Reset all data",
onClick: () => console.log("clicked")}}
]
})
// => null
extensionAPI.ui
extensionAPI.ui.commandPalette.addCommand
roamAlphaAPI.ui.commandPalette.addCommand, but tied to your extension:
label updates the existing command
label (string, required) — text shown in the Command Palette
callback (function, required) — called with no arguments when the user runs the command
disable-hotkey (boolean, optional) — don't allow a hotkey for this command
default-hotkey (string | array of strings, optional) — most commands should NOT set this; without it, users can still assign their own hotkey in Settings → Hotkeys
await extensionAPI.ui.commandPalette.addCommand({
label: "My Extension: Do the thing",
callback: () => console.log("doing the thing")})
// => null
extensionAPI.ui.commandPalette.removeCommand
roamAlphaAPI.ui.commandPalette.removeCommand
label (string, required) — the label the command was added with
await extensionAPI.ui.commandPalette.removeCommand(
{label: "My Extension: Do the thing"})
// => null
extensionAPI.ui.slashCommand.addCommand
/ slash menu — same as roamAlphaAPI.ui.slashCommand.addCommand, but tied to your extension:
label updates the existing command
label (string, required) — text shown in the slash menu
callback (function, required)
{"block-uid": "YnatnbZzF",
"window-id": "BBG4fFwolaVlT5FZQdzAI7P40aB3-body-outline-04-15-2021",
indexes: [1, 10]}
display-conditional (function, optional) — called with the context object (without indexes); return true to show the command
extensionAPI.ui.slashCommand.addCommand({
label: "Insert Greeting",
callback: (context) => "Hello from my extension! 👋"})
// => null
extensionAPI.ui.slashCommand.removeCommand
roamAlphaAPI.ui.slashCommand.removeCommand
label (string, required) — the label the command was added with
extensionAPI.ui.slashCommand.removeCommand(
{label: "Insert Greeting"})
// => null
extensionAPI.ai
extensionAPI.ai.addTool #experimental
roamAlphaAPI.ai.addTool #experimental, but tied to your extension:
name updates the existing tool instead of adding a second one.
name (string, required) — the tool's name; up to 64 characters from A-Za-z0-9_-
description (string, required) — tells the agent what the tool does and when to use it; up to 2000 characters
handler (function, required)
context object (below); returns a JSON-serializable value or a Promise resolving to one
context.tokenUserUid (string) — uid of the AI user the call came from; absent when the tool is invoked directly from JS
user-uid argument (e.g. block: {..., "user-uid": context.tokenUserUid}) to attribute writes to the AI — including writes made after the handler's first await.
context.asTokenUser(fn) — runs fn with the graph writes it issues attributed to the calling AI, with the AI's token scopes in effect
scope (string, optional, default "edit") — "read" | "append" | "edit"
inputSchema (object, optional)
handler's args
$schema, if present, must be the draft-07 URI — schemas are always validated as draft-07.
type, if present, must be "object" — a tool's args are always a plain object.
handler runs; when absent, args reach the handler unvalidated.
null
extensionAPI.ai.addTool({
name: "word-count",
description: "Counts words in a block's text.",
scope: "read",
inputSchema: {
type: "object",
properties: {uid: {type: "string", description: "The block's uid"}},
required: ["uid"]
},
handler: ({uid}) => {
const s = window.roamAlphaAPI.pull("[:block/string]", [":block/uid", uid])[":block/string"] || "";
return {words: s.trim() ? s.trim().split(/\s+/).length : 0};
}
}) // => null
extensionAPI.ai.removeTool #experimental
roamAlphaAPI.ai.removeTool #experimental
name (string, required) — the name the tool was added with
null
extensionAPI.ai.removeTool({name: "word-count"}) // => null