Quick start
Let Knight Note create the boilerplate.
- 1
Open a vault, then go to Settings -> Plugins.
- 2
Select Create Starter. Knight Note creates a plugin folder with a manifest and a working command.
- 3
Use Open plugin folder and edit
main.js,manifest.json, orstyles.css. - 4
Enable the plugin, then use Reload plugin after code changes.
The files
A plugin can be very small.
manifest.jsonName, version, entry point, and requested access.
main.jsYour commands and plugin logic.
styles.cssOptional styles for views or interface elements.
data.jsonCreated 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.
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 palettevault.listNotes(),readNote(),createNote(), andupdateNote()to work with notessettings.readPluginData()andwritePluginData()for plugin-owned stateui.showNotice()for short feedbackrequestUrl()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:writeCapture 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 workspaceUse 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 workspaceClipboard 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 workspaceAdd 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 workspaceloadData() 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
workspaceKeep 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 workspaceAdd 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.
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.
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.