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

Settings, events and services

.md

How a plugin stores settings, listens to events, and shares features with other plugins through services.

Three tools connect a plugin to the rest of Loam. Settings are options the user can change. Events tell a plugin that something happened. Services let a plugin use a feature that another plugin provides. Use settings for choices the user makes, events to react to a change, and services when another plugin needs a feature.

Settings

Describe your settings as one Codable struct with default values. Read it with context.settings.value(default:), and save it with context.settings.update(_:). Loam stores the values in .loam/config.json, under plugins.<id>.settings.

struct Settings: Codable, Equatable {
    var folder = "Habits"
    var week = "monday"
}

let settings = context.settings.value(default: Settings())

Settings > Plugins shows every key with its current value, without any UI code. For titles, help text and a fixed list of choices, describe the keys in the manifest:

PluginManifest(id: "word-count", name: "Word Count", version: "0.1.0", settings: [
    SettingDescription("folder", title: "Folder", help: "Where the new notes go.", default: .string("Habits")),
    SettingDescription("week", title: "Week starts on", choices: [
        SettingChoice(.string("monday"), title: "Monday"), SettingChoice(.string("sunday"), title: "Sunday"),
    ]),
])
  • A key missing from the file takes its default, so adding a setting never breaks an existing vault.
  • An agent changes a setting with loam plugins.set-setting id=<id> key=<key> value=<JSON>, and lists them with loam plugins.settings id=<id>.
  • A value of the wrong type is refused, and nothing is written.
  • Listen for PluginSettingsChanged and read the settings again, so a change applies at once.

Events

An event is a struct that conforms to PluginEvent. Listen with context.on. Use context.intercept to change or stop an event: call next(event) to continue, and return without calling it to stop. Send your own with context.emit.

context.on(VaultChanged.self) { event in
    for change in event.noteChanges {
        // change.path is a note that appeared, changed or disappeared.
    }
}
  • VaultChanged: files appeared, changed or disappeared on disk.
  • NoteWillChange and NoteDidChange: sent around a write by context.vault.
  • NotesMoved: notes moved, so open views can follow their new paths.
  • PluginSettingsChanged: the settings of a plugin changed.

Services

A service is a protocol that one plugin provides and others use. A plugin never imports another plugin. It names the service by id, and Loam connects the two:

public protocol Greeter: AnyObject {
    func greet(_ name: String) -> String
}

extension ServiceKey where Service == Greeter {
    static var greeter: ServiceKey<Greeter> { ServiceKey("greeter") }
}
  • Provider: list "greeter" in provides, then call try context.provide(.greeter, implementation) in activate.
  • User: list "greeter" in requires, then call context.service(.greeter). Loam starts the provider first.
  • optionalRequires: use the service only when it exists. Your plugin restarts when the provider comes back.
  • Two plugins that provide the same service are refused, so a plugin can be replaced by another one that provides the same service.

Loam itself provides services such as note-graph, note-query, tasks, daily-notes, files and attachments. loam plugin list shows what each plugin provides and requires.

For AI agents: loam guide plugins shows these APIs in context. loam plugins.settings id=<id> lists the settings of one plugin.