Loam is free and open source for macOS. Your notes stay plain Markdown files on your disk. Download for macOS.[Download for macOS.]
All pages

Plugins

Interface plugins

.md

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 .leading or .trailing sidebar, 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 example workspace.open(.file(path), placement: .replace). The placements are replace, newTab and beside.
  • 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 list editor in optionalRequires to be restarted when it comes.

Style and permissions

  • Take colours from the themeTokens environment value, and animations from Motion in PluginKitUI. Honour Reduce Motion.
  • The generated plugin.json of a UI plugin already sets "permissions": ["ui", "vault.read"]. Add vault.write to permissions only 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 relaunch

loam 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.

For AI agents: loam guide ui lists the registration calls. loam guide app lists the commands that drive the open window.