Plugins
Interface plugins
Add pages, panels, slash menu items, window overlays and settings sections to the Loam window with a UI plugin.
A UI plugin adds parts to the Loam window: pages that open in a tab, panels in the sidebars or the status bar, items in the / menu of a note, and views that float over the window. Everything it registers is removed when the plugin turns off. Keep the data and the commands in a logic module, so the CLI and agents can do the same work without a window.
What a UI plugin can add
registerView(ViewType(...)): a page that opens in a tab.registerPanel(Panel(...)): a panel in the.leadingor.trailingsidebar, or in the.statusBar.addSidebarLink(SidebarLink(...)): a row in the navigation at the top of the left sidebar.addSlashItem(SlashItem(...)): an item in the/menu of notes. It does nothing when no editor is active.addWindowOverlay(WindowOverlay(...)): a view over the bottom-trailing corner of the window.addSettingsSection(SettingsSection(...)): a section of the plugin page in Settings.registerCommand(Command(...)): an action for the palette, the CLI and agents.
A minimal UI plugin
This is the activateUI function that loam plugin new word-panel --ui generates. It adds a panel on the right side and a command. A UI plugin conforms to UIPlugin, and activateUI runs on the main actor:
Sources/WordPanel/WordPanelPlugin.swift
@MainActor
public func activateUI(context: PluginContext) throws {
let counter = NoteCounter(vault: context.vault)
try context.registerPanel(Panel(
id: "word-panel.panel",
title: "Word Panel",
symbol: "hand.wave",
slot: .trailing
) {
AnyView(WordPanelPanel(notes: counter.count()))
})
try context.registerCommand(Command(
id: "word-panel.hello",
title: "Word Panel: say hello",
summary: "Says hello and how many notes the vault has. No arguments."
) { _ in
await MainActor.run { "Hello from Word Panel. Notes in this vault: \(counter.count())." }
})
}The view is SwiftUI. To refresh it when notes change, listen for VaultChanged and reload.
Reach the window through contracts
A UI plugin reaches the window only through the services in its context:
context.workspace: opens a resource, for exampleworkspace.open(.file(path), placement: .replace). The placements arereplace,newTabandbeside.context.quickPick: a filterable list in a popup.context.toasts?.show(_:isError:): a short message about work that ends on its own.context.editor: the Markdown editor. It is nil until an editor plugin is active, so listeditorinoptionalRequiresto be restarted when it comes.
Style and permissions
- Take colours from the
themeTokensenvironment value, and animations fromMotionin PluginKitUI. Honour Reduce Motion. - The generated
plugin.jsonof a UI plugin already sets"permissions": ["ui", "vault.read"]. Addvault.writetopermissionsonly if a view writes notes.
Check it in the app
The app loads a rebuilt library only after a restart. Rebuild the plugin, then restart Loam on the vault:
loam --vault ~/Notes plugin build word-panel
loam --vault ~/Notes app relaunchloam app talks to the window that has the vault open, so the app must be open on it. Open the right sidebar with loam app run sidebar.toggle-trailing, then take a picture of the window with loam app shot <file.png> and look at it.
loam guide ui lists the registration calls. loam guide app lists the commands that drive the open window.