Readied Docs
Plugins

API Reference

Complete reference for the Readied plugin API — PluginContext, EditorAPI, LayoutManager, and more

API Reference

The plugin API is provided through the PluginContext object passed to your activate() function.

activate(context: PluginContext): PluginDisposable | void

PluginContext

PropertyTypeDescription
editorEditorAPIRead and manipulate editor content
appAppAPIAccess notes, notebooks, and app events
layoutLayoutManagerAdd UI components to layout zones
decorationsEditorDecorationAPIAdd line highlights and widgets
configPluginConfigAPIRead/write plugin configuration
logPluginLoggerLog messages to the plugin console

Methods

registerCommand(options, execute)

Register a command in the command palette.

const unregister = context.registerCommand(
  {
    id: 'my-command',
    name: 'Do Something',
    keybinding: { key: 'D', modifiers: ['Mod', 'Shift'] },
    icon: 'Wand',
  },
  () => {
    // Your logic here
    return true; // handled
  }
);

PluginCommandOptions:

FieldTypeRequiredDescription
idstringYesUnique command ID within your plugin
namestringYesDisplay name in command palette
keybindingobjectNoKeyboard shortcut
iconstringNoLucide icon name
categorystringNoCommand category
showInPalettebooleanNoShow in palette (default: true)

Keybinding modifiers: 'Mod' (Cmd on Mac, Ctrl on Windows), 'Shift', 'Alt'

registerExtensions(id, extensions)

Register CodeMirror 6 extensions for the editor.

import { keymap } from '@codemirror/view';

const unregister = context.registerExtensions('my-ext', [
  keymap.of([
    {
      key: 'Tab',
      run: () => {
        /* ... */ return true;
      },
    },
  ]),
]);

registerRemarkPlugin(id, plugin)

Add a remark plugin to the markdown preview pipeline. Remark plugins operate on the mdast (markdown abstract syntax tree) level. They receive (tree, file) and can transform the tree in place.

Errors thrown by remark plugins are caught and displayed as inline markers in the preview, so a buggy plugin will not break the entire preview.

import type { Root } from 'mdast';
import { visit } from 'unist-util-visit';

function myRemarkPlugin() {
  return (tree: Root) => {
    visit(tree, 'text', node => {
      // Transform text nodes
      node.value = node.value.replace(/TODO/g, '[ ] TODO');
    });
  };
}

context.registerRemarkPlugin('my-remark', myRemarkPlugin);

registerRehypePlugin(id, plugin)

Add a rehype plugin to the preview pipeline. Rehype plugins operate on the hast (HTML abstract syntax tree) level, after markdown has been converted to HTML. They receive (tree, file) and can transform the HTML tree.

Like remark plugins, errors are caught and shown as inline markers in preview.

function myRehypePlugin() {
  return tree => {
    // Transform the HTML AST
  };
}

context.registerRehypePlugin('my-rehype', myRehypePlugin);

registerPreviewComponent(id, tagName, component)

Replace an HTML element in the markdown preview with a custom React component. Every instance of the specified tag in the rendered preview will be replaced with your component.

function CustomAccordion({ children, ...props }) {
  const [open, setOpen] = useState(false);
  return (
    <div className="accordion" onClick={() => setOpen(!open)}>
      {open ? children : <span>Click to expand</span>}
    </div>
  );
}

// Replace all <details> elements with a custom accordion
context.registerPreviewComponent('my-accordion', 'details', CustomAccordion);

registerCodeBlockRenderer(id, language, component)

Render fenced code blocks of a specific language with a custom React component. The component receives { code, language, meta } as props.

function MermaidRenderer({ code, language, meta }: {
  code: string;
  language: string;
  meta?: string;
}) {
  return <div className="mermaid">{code}</div>;
}

context.registerCodeBlockRenderer('mermaid', 'mermaid', MermaidRenderer);

registerCssVariables(id, variables)

Register CSS custom properties for theming.

context.registerCssVariables('my-theme', {
  '--my-plugin-bg': '#1a1b2e',
  '--my-plugin-accent': '#7c3aed',
});

getTheme()

Returns the current resolved theme as 'dark' | 'light'.

const theme = context.getTheme();
// 'dark' or 'light'

onThemeChanged(callback)

Subscribe to theme changes. The callback fires whenever the user switches between dark and light mode. Returns an unsubscribe function.

const unsubscribe = context.onThemeChanged(() => {
  const theme = context.getTheme();
  context.log.info(`Theme changed to ${theme}`);
});

// Later, to stop listening:
unsubscribe();

PluginManifest

The PluginManifest is the top-level object you export from your plugin. It defines metadata, configuration, and the activate() entry point.

FieldTypeRequiredDescription
idstringYesUnique plugin identifier
namestringYesDisplay name
versionstringYesSemver version
descriptionstringNoShort description
configSchemaRecord<string, ConfigField>NoUser-configurable settings
apiVersionstringNoPlugin API version this plugin targets (e.g. "1")
dependenciesRecord<string, string>NoPlugin dependencies: map of pluginId to semver range
themeType'ui'NoMark this plugin as a theme plugin
activate(context) => DisposableYesEntry point, receives PluginContext

Example with new fields:

export const plugin: PluginManifest = {
  id: 'my-theme',
  name: 'My Theme',
  version: '1.0.0',
  apiVersion: '1',
  themeType: 'ui',
  dependencies: {
    'color-utils': '^1.0.0',
  },

  activate(context) {
    // ...
  },
};

Config Validation

The @readied/plugin-api package exports a validateConfigValue() function for validating config values against their schema. This is useful when building custom settings UI or validating values programmatically.

import { validateConfigValue } from '@readied/plugin-api';

const result = validateConfigValue(field, value);
// result: { valid: boolean, error?: string }

Supports all 5 config field types:

TypeValidates
booleanValue is a boolean
stringValue is a string
numberValue is a number
enumValue matches one of the defined options
rangeValue is a number within min/max bounds

Example:

const field = {
  type: 'range',
  default: 10,
  min: 1,
  max: 100,
  step: 1,
  description: 'Max results',
};

validateConfigValue(field, 50); // { valid: true }
validateConfigValue(field, 200); // { valid: false, error: 'Value must be between 1 and 100' }
validateConfigValue(field, 'hi'); // { valid: false, error: 'Expected a number' }

EditorAPI

Access via context.editor.

MethodReturnsDescription
getContent()stringFull editor content
getSelection(){ from, to }Current selection range
replaceRange(from, to, text)voidReplace text between positions
insertAtCursor(text)voidInsert text at cursor
getWordCount()numberWord count
getCharCount()numberCharacter count
getLineCount()numberLine count
onDocChanged(callback)() => voidSubscribe to content changes
onSelectionChanged(callback)() => voidSubscribe to selection changes
focus()voidFocus the editor

Example: Word frequency counter

const content = context.editor.getContent();
const words = content.split(/\s+/).filter(Boolean);
const freq = new Map<string, number>();
words.forEach(w => {
  const lower = w.toLowerCase();
  freq.set(lower, (freq.get(lower) || 0) + 1);
});

AppAPI

Access via context.app. All methods are read-only.

MethodReturnsDescription
getCurrentNote()NoteInfo | nullCurrently open note
searchNotes(query)Promise<Array>Search notes by text
getNoteById(id)Promise<NoteInfo | null>Get note by ID
getNoteTags(noteId)Promise<string[]>Get tags for a note
getBacklinks(noteId)Promise<Array>Get notes linking to this note
listNotes()Promise<NoteSummaryInfo[]>List all notes
listNotebooks()Promise<NotebookInfo[]>List all notebooks
listTags()Promise<string[]>List all tags
onNoteSelected(callback)() => voidNote selection event
onNoteCreated(callback)() => voidNote creation event
onNoteDeleted(callback)() => voidNote deletion event

NoteInfo type:

interface NoteInfo {
  id: string;
  title: string;
  content: string;
}

LayoutManager

Access via context.layout. See Layout Zones for zone details.

MethodDescription
addComponent(zone, entry)Add a React component to a zone
removeComponent(id)Remove a component by ID
removeAllForPlugin(pluginId)Remove all components for a plugin

ZoneEntry:

context.layout.addComponent('editor-status-bar', {
  id: 'my-plugin:status',
  component: MyStatusComponent,
  order: 10, // Lower = further left
  meta: { editor: context.editor },
});

Your component receives { meta } as props:

function MyStatusComponent({ meta }: ZoneComponentProps) {
  const editor = meta?.editor as EditorAPI;
  return <span>{editor.getWordCount()} words</span>;
}

EditorDecorationAPI

Access via context.decorations.

MethodReturnsDescription
addLineHighlight(line, className)() => voidHighlight a line (1-indexed)
addWidget(pos, dom)() => voidAdd a DOM widget at position
clear()voidRemove all decorations

PluginConfigAPI

Access via context.config. Values persist across sessions.

MethodDescription
get<T>(key)Read a config value
set(key, value)Write a config value

Define your config schema in the manifest:

export const plugin: PluginManifest = {
  // ...
  configSchema: {
    apiKey: {
      type: 'string',
      default: '',
      description: 'API key for the service',
    },
    maxResults: {
      type: 'range',
      default: 10,
      min: 1,
      max: 100,
      step: 1,
      description: 'Maximum results to show',
    },
    theme: {
      type: 'enum',
      default: 'auto',
      options: [
        { value: 'auto', label: 'Auto' },
        { value: 'light', label: 'Light' },
        { value: 'dark', label: 'Dark' },
      ],
      description: 'Plugin theme',
    },
    showNotifications: {
      type: 'boolean',
      default: true,
      description: 'Show notification popups',
    },
  },
};

Config fields automatically render in Settings > Plugins when users expand your plugin's settings.


PluginLogger

Access via context.log.

MethodDescription
info(message, ...args)Log info message
warn(message, ...args)Log warning
error(message, ...args)Log error

Messages appear in the developer console prefixed with your plugin ID.