Skip to content

Lapis manifest contributions

Every community plugin starts with the baseline Obsidian manifest.json fields. Lapis-native extensions can add an optional lapis namespace for contribution indexing, lazy activation, and declarative settings.

If you are coming from VS Code, compare this to contributes in package.json. Lapis keeps Obsidian-compatible fields at the top level and puts Lapis-specific metadata under lapis.

{
"id": "example-plugin",
"name": "Example Plugin",
"version": "1.0.0",
"minAppVersion": "1.7.7",
"main": "main.js",
"lapis": {
"manifestVersion": 1,
"extensionKind": ["workspace", "trustedDesktop"],
"activationEvents": ["onCommand:example-plugin:lint"],
"permissions": ["vault.read", "commands"],
"contributes": {
"commands": [],
"configuration": [],
"languages": [],
"editorViews": [],
"services": [],
"statusBarItems": []
},
"runtime": {
"workspace": "main.js",
"desktop": "desktop.js"
}
}
}

If lapis is absent, the plugin is treated as a standard Obsidian-compatible community plugin.

FieldPurpose
manifestVersionSchema version for the lapis namespace (currently 1)
extensionKindSupported hosts: workspace, browserWorker, trustedDesktop
activationEventsLazy activation triggers (see below)
permissionsRequested brokered capabilities such as vault.read or settings.read
contributesDeclarative contribution records
runtimeHost-specific entry files when they differ from main

Lapis extensions can defer running main.js until a contribution needs code:

EventMeaning
onStartupFinishedActivate after workspace boot completes
onCommand:<id>Activate before executing a contributed command
onLanguage:<languageId>Activate when a matching document opens
onView:<viewType>Activate before creating a contributed view
onService:<serviceId>Activate when a service provider is selected
onFileSystem:<glob>Activate when matching vault files are opened
workspaceContains:<glob>Activate after discovery finds a matching file

Obsidian-compatible plugins without lapis metadata still activate during the normal plugin boot phase.

{
"command": "example-plugin:lint",
"title": "$(play) Run lint",
"category": "Example",
"when": "editor.active && editor.language == markdown"
}

Manifest commands install without running plugin code. Execution triggers lazy activation when the plugin has not loaded yet.

Declarative settings use JSON Schema properties, similar to VS Code contributes.configuration:

{
"id": "example-plugin",
"title": "Example Plugin",
"properties": {
"enabled": {
"type": "boolean",
"title": "Enable linting",
"default": true
},
"severity": {
"type": "string",
"enum": ["error", "warning", "info"],
"enumItemLabels": ["Errors only", "Warnings", "All messages"],
"default": "warning"
}
}
}

Supported control mappings include boolean toggles, text inputs, enums, sliders (number with min/max), multiline text, color and icon pickers, date/time fields, primitive arrays, flat object grids, and record-object maps. Complex nested schemas fall back to an Edit in settings.json button.

Compared to VS Code configuration contributions, Lapis supports the common scalar and collection shapes plus Lapis-specific format: "icon". VS Code-only metadata such as scope, tags, and keywords are not used.

{
"id": "typescript",
"aliases": ["TS"],
"extensions": [".ts", ".tsx"]
}

Language contributions are indexed for activation and diagnostics. Registering the CodeMirror parser still requires plugin code through registerEditorExtension().

{
"id": "example.editor",
"label": "Example editor",
"filenamePatterns": ["*.example"],
"priority": "default"
}

Editor-view metadata lets Settings show selectable editor IDs and preserves workspace.editorAssociations when a plugin is disabled. You still register the view constructor with registerView() and wire extensions with registerExtensions().

{
"id": "markdown-lint",
"service": "language-service",
"languages": ["markdown"],
"priority": 100,
"capabilities": {
"diagnostics": true,
"codeActions": true
}
}

The bundled Markdown Lint plugin uses this pattern. A system extension binds the actual provider implementation at boot. Hybrid community plugins can call Plugin.registerLapisServiceProvider() from code instead.

{
"id": "example.status",
"text": "Ready",
"alignment": "left",
"command": "example-plugin:lint",
"when": "editor.active"
}

Declarative contributions can declare a when expression evaluated through App.contextKeys.

Built-in keys include:

  • editor.active, editor.language, editor.hasSelection
  • view.id, view.focused
  • workspace.trusted
  • runtime.host, runtime.desktop, runtime.browser, runtime.nativeHost
  • plugin.enabled.<pluginId>, plugin.state.<pluginId>

Plugins can register custom scoped keys under plugin.<pluginId>.*. Unload resets those keys automatically.

Supported grammar:

  • Identifiers with dot or dash namespaces (editor.active)
  • &&, ||, unary !
  • ==, !=
  • Parentheses for grouping
  • Boolean, string, and number literals
ClassWhenRequired entry
Obsidian-compatibleNo lapis namespacemain.js
Lapis extensionlapis only, no Obsidian-compat requirementSelected runtime entry if any
HybridBoth Obsidian and lapis metadatamain.js plus any Lapis runtime entries

The bundled markdown-lint plugin is manifest-heavy:

{
"id": "markdown-lint",
"name": "Markdown Lint",
"version": "0.0.1",
"minAppVersion": "1.7.7",
"lapis": {
"manifestVersion": 1,
"source": "system",
"contributes": {
"configuration": [
{
"id": "markdown-lint",
"title": "Markdown Lint",
"properties": {
"disabledRules": {
"type": "array",
"title": "Disabled rules",
"items": { "type": "string" },
"default": []
}
}
}
],
"services": [
{
"id": "markdown-lint",
"service": "language-service",
"languages": ["markdown"],
"priority": 100,
"capabilities": {
"diagnostics": true,
"codeActions": true
}
}
]
}
}
}