Plugin developer guide

Build the tool
you wish you had.

Knight Note plugins are JavaScript files that run inside a vault. Start with a working example, add only the access your plugin needs, and reload it while you build.

Quick start

Let Knight Note create the boilerplate.

  1. 1

    Open a vault, then go to Settings -> Plugins.

  2. 2

    Select Create Starter. Knight Note creates a plugin folder with a manifest and a working command.

  3. 3

    Use Open plugin folder and edit main.js, manifest.json, or styles.css.

  4. 4

    Enable the plugin, then use Reload plugin after code changes.

The files

A plugin can be very small.

manifest.json

Name, version, entry point, and requested access.

main.js

Your commands and plugin logic.

styles.css

Optional styles for views or interface elements.

data.json

Created by Knight Note when your plugin saves settings or state.

A minimal manifest

{
  "id": "hello-knight-note",
  "name": "Hello Knight Note",
  "version": "0.1.0",
  "description": "Adds a hello command.",
  "author": "Your name",
  "main": "main.js",
  "knightnotePermissions": ["workspace"]
}

A minimal plugin

const { Plugin, ui, workspace } = require('knightnote');

module.exports = class HelloPlugin extends Plugin {
  async onload() {
    workspace.addCommand({
      id: 'say-hello',
      name: 'Say hello',
      callback: () => ui.showNotice('Hello from your plugin'),
    });
  }
};

Requested access

Ask for only what you use.

Knight Note shows these permissions before a user enables the plugin.

PermissionUse it when your plugin needs to...
vault:readList notes, read note content, or inspect links.
vault:writeCreate, update, append to, or delete notes.
workspaceAdd commands, views, ribbon buttons, notices, or status items.
settingsStore plugin settings or state in data.json.
clipboardRead from or write to the system clipboard.
networkMake outbound requests with requestUrl().

API basics

Start with the native Knight Note API.

Import only the parts you need from knightnote. The most common building blocks are:

  • workspace.addCommand() to add an action to the command palette
  • vault.listNotes(), readNote(), createNote(), and updateNote() to work with notes
  • settings.readPluginData() and writePluginData() for plugin-owned state
  • ui.showNotice() for short feedback
  • requestUrl() for permission-checked network requests

The starter also demonstrates Knight Note's extended plugin API. Keep its module line when you need editor access, frontmatter helpers, custom views, ribbon actions, or status items.

Build recipes

The patterns behind the official plugins.

These examples are deliberately small. Add them to the starter, keep its existing first line, and declare the permission shown with each recipe.

Read, create, and update notes

vault:read vault:write

Capture the active vault before an asynchronous operation and check it again before writing. This prevents a delayed task from changing a different vault.

const originalVault = app.getActiveVault();
if (!originalVault) return ui.showNotice('Open a vault first.');

const notes = vault.listNotes();
const markdown = await vault.readNote(notes[0].id);

if (app.getActiveVault()?.id !== originalVault.id) return;
const created = await vault.createNote('My summary', markdown);
await workspace.openNote(created.id);

When a plugin updates a note that it owns, place a unique marker in the note and verify that marker before overwriting it. Do not identify plugin-owned notes by title alone.

Edit the current note or its frontmatter

vault:write workspace

Use the active editor for cursor-based tools. Use the frontmatter helper when you only need to change metadata.

const editor = this.app.workspace.activeEditor?.editor;
if (!editor) return new Notice('Open an editable note first.');
editor.replaceSelection(new Date().toISOString());

const file = this.app.workspace.getActiveFile();
if (!file) return;
await this.app.fileManager.processFrontMatter(file, data => {
  data.reviewed = true;
  data.reviewedAt = new Date().toISOString();
});

Read the clipboard

clipboard workspace

Clipboard access is permission-checked. Validate copied URLs before saving them and reject credentials, unsupported protocols, control characters, and unreasonably long input.

const value = await this.app.clipboard.readText();
const url = new URL(value.trim());
if (url.protocol !== 'https:' || url.username || url.password) {
  return new Notice('Copy a credential-free HTTPS link.');
}

Make a network request

network workspace

Add requestUrl to the names imported by the starter. Knight Note blocks direct browser networking; plugin requests go through this permission-checked helper.

const response = await requestUrl({
  url: 'https://example.com/health',
  method: 'HEAD',
  throw: false,
});

new Notice(response.status < 400 ? 'Link is reachable.' : 'Link needs review.');

Save state and run a timer

settings workspace

loadData() and saveData() store JSON for the current plugin and vault. Register cleanup for timers and other long-lived work.

async onload() {
  const saved = await this.loadData() || { deadline: 0 };
  const status = this.addStatusBarItem();
  status.setText(saved.deadline ? 'Timer running' : 'Timer ready');

  const timer = globalThis.setInterval(() => {
    status.setText(new Date().toLocaleTimeString());
  }, 1000);

  this.register(() => globalThis.clearInterval(timer));
}

Follow the app language

workspace

Keep a small English fallback and update command or view labels when Knight Note switches between English and German.

const labels = {
  en: { command: 'Open daily note' },
  de: { command: 'Tagesnotiz öffnen' },
};

const language = app.getUiLanguage() === 'de' ? 'de' : 'en';
const removeLanguageListener = app.onUiLanguageChange(() => {
  this.registerCommandsForCurrentLanguage();
});
this.register(removeLanguageListener);

Add a custom view, ribbon action, and refresh events

vault:read workspace

Add ItemView to the names imported by the starter. Register the view once, then open it from a command or ribbon button. Use registered events so Knight Note removes every listener when the plugin unloads.

class GraphView extends ItemView {
  getViewType() { return 'my-graph-view'; }
  getDisplayText() { return 'My graph'; }
  async onOpen() { this.contentEl.setText('Build the graph here'); }
}

this.registerView('my-graph-view', leaf => new GraphView(leaf));
this.addRibbonIcon('network', 'Open my graph', () => this.openGraph());
this.registerEvent(this.app.vault.on('modify', () => this.refreshGraph()));
this.registerEvent(this.app.workspace.on('file-open', () => this.refreshGraph()));

For note connections, use this.app.metadataCache.getResolvedLinks(). For complete note metadata, use this.app.vault.getMarkdownFiles(). Treat returned data as a snapshot and rebuild after relevant vault events.

Documentation audit

What you can build with this guide.

The recipes above cover the building blocks used by every plugin in the current official collection.

PluginMain patterns
Daily FocusCommands, note ownership markers, create/open note, language changes
Meeting NotesResponsive workspace, managed-note parsing, exact-source updates, safe titles, language changes
Task DashboardCustom view, task search and filters, source navigation, verified note updates, legacy owned note
Link InboxResponsive review queue, clipboard validation, exact-source entry mutations, saved path state, language changes
Link Health CheckerResponsive review view, bounded request queue, privacy-safe URL display, status filters, source navigation, language changes
Markdown CleanerResponsive before/after review, configurable transformations, protected regions, exact editor and source revalidation, language changes
Frontmatter StatusResponsive custom view, live active-file metadata, confirmed owned-field reset, per-note mutation queues, source navigation, language changes
Pomodoro FocusResponsive focus center, accessible countdown, pause/resume, focus and break cycles, bounded saved state, clock safety, language changes
Timestamp ToolsResponsive preview studio, exact frozen values, editor and vault revalidation, per-editor queues, source navigation, language changes
Second BrainCustom view, ribbon, vault events, note and link metadata

Share your plugin

Local first, share when ready.

A plugin does not need to be in the official collection. Another user can install a plugin folder or ZIP from Settings -> Plugins. Include manifest.json, main.js, optional styles.css, and any local files your code imports. Do not include data.json, secrets, tokens, or test-vault content.

Before you send it

Increase the version, check the minimum Knight Note version, install the package into a clean test vault, and confirm that the requested-access list matches what the code actually uses.

Test locally

Reload, check, repeat.

Use the plugin row in Settings -> Plugins to reload after editing. Load errors appear on that row. If a plugin causes trouble, turn off Enable plugins to start in plugin safe mode.

Before sharing a plugin

Test it in a separate vault, declare every permission it uses, handle missing notes and vault changes, avoid overlapping writes, bound the amount of content you process, and never include secrets in the plugin folder.