Plugin Migration Guide from v5 to v6

New ipm CLI tool

In v6, there is a new CLI tool named ipm (Inkdrop Plugin Manager) for publishing plugins.

npm install -g @inkdropapp/ipm-cli

ipm configure

Check out the repository for more details.

New TypeScript definitions

TypeScript definitions for Inkdrop v6 are now available at @inkdropapp/types. You can install them to get type-checking and autocompletion for the Inkdrop API in your plugin:

npm install --save-dev @inkdropapp/types

@electron/remote is deprecated

remote.dialoginkdrop.dialog

Before:

const remote = require('@electron/remote')
const { dialog } = remote

return dialog.showOpenDialog(
  inkdrop.window,
  {
  ...
})

After:

return inkdrop.dialog.showOpenDialog({
  ...
})

inkdrop.window.on()

Before:

inkdrop.window.on('focus', this.handleAppFocus)

After:

const sub = inkdrop.window.onFocus(this.handleAppFocus)

// Unsubscribe
sub.dispose()

activate() receives the inkdrop environment

In v6, the plugin's activate() function is called with the inkdrop environment instance as the first argument, followed by the persisted package state.

Before:

module.exports = {
  activate() {
    inkdrop.components.registerClass(MyComponent)
  }
}

After:

import type { Environment } from '@inkdropapp/types'

module.exports = {
  activate(env: Environment) {
    env.components.registerClass(MyComponent)
  }
}

Since the inkdrop environment is now passed as an argument rather than read from the global scope, you need a way to share it across the other modules of your plugin. A common pattern is to capture the instance in a dedicated module that exposes a getter and setter:

import type { Environment } from '@inkdropapp/types'

/**
 * Captures the `Environment` instance handed to `activate()` so the plugin's
 * other modules can reach it without touching the (discouraged) global
 * `inkdrop` variable.
 */
let captured: Environment | undefined

export function setEnv(env: Environment | undefined): void {
  captured = env
}

export function getEnv(): Environment {
  if (!captured) {
    throw new Error('env accessed before activate()')
  }
  return captured
}

Then call setEnv from activate() so the rest of your plugin can retrieve the environment via getEnv(), and clear it in deactivate() to avoid holding a stale reference:

import type { Environment, IInkdropPlugin } from '@inkdropapp/types'
import { setEnv } from './env'

class YourPlugin implements IInkdropPlugin {
  activate(env: Environment) {
    setEnv(env)
    // initialize your plugin
  }

  deactivate(env: Environment) {
    // cleanup
    setEnv(undefined)
  }
}

export default new YourPlugin()

onEditorLoadensureEditorLoaded

In v6, you can use ensureEditorLoaded instead of onEditorLoad to run code with the active editor. onEditorLoad only fires when the editor component loads, so if the editor was already loaded by the time your plugin subscribed, you had to check for an active editor yourself and then also subscribe for future loads. ensureEditorLoaded handles both cases: it invokes the callback immediately if an editor is already loaded, and otherwise waits for the next one to load.

Before:

function extendEditor() {
  // ...
}

const editor = inkdrop.getActiveEditor()
if (editor) extendEditor()
inkdrop.onEditorLoad(() => {
  extendEditor()
})

After:

inkdrop.ensureEditorLoaded((editor: EditorView) => {
  // extend the editor
})

You no longer have to do the same dance of checking for an already-active editor before subscribing.

inkdrop.main.dataStore.getLocalDB()inkdrop.localDB

CSS selectors for keymaps and commands

  • .mde-preview.mde-preview-container

LESS is deprecated

The necessity of LESS has been less and less over the years, since CSS supports nested selectors and variables. In v6, the app has dropped support for LESS.

  • Rename .less to .css

Before:

@primary-color: #ff0000;
.my-plugin {
  .component {
    color: @primary-color;
  }
}

After:

:root {
  --primary-color: #ff0000;
}
.my-plugin {
  .component {
    color: var(--primary-color);
  }
}

Electron's clipboard is deprecated

Accessing the system clipboard through Electron's clipboard module is no longer supported. Use env.clipboard instead, which proxies the system clipboard over IPC.

Before:

const { clipboard } = require('electron')

const text = clipboard.readText()
clipboard.writeText('Hello, Inkdrop!')

After:

const text = env.clipboard.readText()
env.clipboard.writeText('Hello, Inkdrop!')

UI themes

UI themes (packages with "theme": "ui" in package.json) style the app chrome — sidebar, note list, toolbar, drop-downs, modals — by overriding CSS variables in :root. That model is unchanged in v6: you still set the same component variables (--sidebar-background, --note-list-bar-background, --editor-background, …). What changed is the primitive color tokens those variables build on. Like every other plugin, UI themes also drop LESS for plain CSS and should bump engines.inkdrop to ^6.x.

The stock Solarized Light UI and Solarized Dark UI themes have been updated for v6 and make good references.

Update the manifest and tooling

Bump the engine, the dev-helpers (they provide the v6 dev-server and generate-palette binaries), and add a prepublishOnly script so palette.json stays current:

Before:

{
  "devDependencies": {
    "@inkdropapp/theme-dev-helpers": "^0.3.6"
  },
  "engines": {
    "inkdrop": "^5.9.0"
  }
}

After:

{
  "scripts": {
    "prepublishOnly": "generate-palette"
  },
  "devDependencies": {
    "@inkdropapp/theme-dev-helpers": "^0.4.0"
  },
  "engines": {
    "inkdrop": "^6.0.0"
  }
}

The styleSheets, theme, and themeAppearance fields are unchanged.

Use the v6 design tokens

The biggest thing to be aware of is that the shared color palette was overhauled. v6's @inkdropapp/css (loaded before your theme) provides a set of Tailwind-inspired design tokens to build on instead of hard-coding colors:

  • A neutral grayscale ramp, --hsl-neutral-50--hsl-neutral-950 (50 = lightest, 950 = darkest)
  • Accent families at the same scale — blue, cyan, green, yellow, orange, red, violet, pink — e.g. --hsl-blue-500, --hsl-red-600
  • --color-<family>-<step> wrappers (e.g. --color-neutral-50) that resolve the matching --hsl-* triplet to an hsl() color
:root {
  --page-background: var(--color-neutral-50);
  --text-color: var(--color-neutral-600);
  --primary-color: hsl(var(--hsl-blue-500) / 90%);
}

If your v5 theme referenced palette tokens that no longer exist, repoint them at the tokens above. (For example, Solarized Light dropped its old base16-style grayscale and semantic aliases in favor of the neutral ramp, and switched its --hsl-magenta-* references to --hsl-pink-*.) The full set of color tokens is published in the @inkdropapp/css repository.

Override the built-in variables

Colors are only part of it. The app defines named CSS variables for nearly every UI surface, and your theme overrides just the ones you want to change. For the full list, refer to the source CSS files in @inkdropapp/css, which the app loads before your theme:

  • tokens.css — design tokens
  • ui.css — the main file that defines CSS variables for UI components (sidebar, note list, editor chrome, modals, …)
  • status.css — note status colors (--note-status-*)
  • tags.css — tag colors (--tag-*)
  • task-progress.css — task progress view colors (--task-progress-view-*)

Rather than repointing every component variable by hand, keep a custom palette by redefining the primitive ramps themselves in a tokens.css listed first in styleSheets. Because the app's component variables resolve through --hsl-neutral-* / --hsl-<accent>-*, overriding those ramps recolors the whole UI from one place; your theme.css then only needs the handful of component variables that differ from the default.

/* tokens.css — map your palette onto the v6 ramps */
:root {
  --hsl-neutral-50: 44deg 87% 94%;
  --hsl-neutral-100: 46deg 42% 88%;
  /* …through --hsl-neutral-950… */

  --hsl-blue-500: 205deg 69% 49%;

  /* re-declare the --color-* wrappers so they pick up the overrides */
  --color-neutral-50: hsl(var(--hsl-neutral-50));
  /* … */
}

Move preview styling out

If your v5 UI theme styled preview content directly (e.g. .mde-preview h1, .mde-preview em), drop those rules — preview content belongs in a dedicated preview theme. For any UI-level rule you keep, note that the container class was renamed .mde-preview.mde-preview-container (see above).

Generate palette.json

v6 themes ship a palette.json — a JSON dump of the theme's variables, used for theme previews and (in the future) the mobile app. Generate it with the generate-palette binary from @inkdropapp/theme-dev-helpers:

npx generate-palette

Commit the result, and the prepublishOnly script above keeps it in sync on every publish. See Creating a UI Theme for the full dev loop (npx dev-server, hot reloading, and the component preview).

Syntax themes

Syntax themes (packages with "theme": "syntax" in package.json) styled CodeMirror 5 in v5. Inkdrop 6 runs on CodeMirror 6, so a theme moves from styling CodeMirror's DOM directly to setting CSS variables, and from CodeMirror 5 mode tokens (.cm-*) to CodeMirror 6 / Lezer highlight classes (.tok-*).

The stock default-light and default-dark themes are maintained as reference implementations. Like every other plugin, themes also drop LESS for plain CSS, and should bump engines.inkdrop to ^6.x.

Editor chrome: DOM selectors → CSS variables

You no longer style CodeMirror's DOM. Replace the old .CodeMirror-* rules with the corresponding --editor-* variable, set in :root:

v5 (CodeMirror 5)v6 (CSS variable)
.CodeMirror { background }--editor-background-color
.CodeMirror { color }--editor-foreground-color
.CodeMirror-cursor border / fat cursor--editor-caret-color
.CodeMirror-selected--editor-selection-background
.CodeMirror-focused .CodeMirror-selected--editor-focused-selection-background
.CodeMirror-gutters--editor-gutter-background-color, --editor-gutter-border-right
.CodeMirror-linenumber--editor-gutter-color
.CodeMirror-activeline-background--editor-active-line-background-color
.CodeMirror-matchingbracket--editor-matching-bracket-outline, --editor-matching-bracket-background-color

See the full list of --editor-* and --md-* variables at the top of the reference theme's styles/index.css.

Syntax tokens: .cm-*.tok-*

CodeMirror 5 mode tokens become CodeMirror 6 / Lezer highlight classes, scoped under .cm-editor (and .mde-preview .codeblock for rendered preview code blocks):

v5 (CodeMirror 5)v6 (Lezer highlight class)
.cm-keyword.tok-keyword
.cm-string.tok-string
.cm-comment.tok-comment
.cm-number.tok-number
.cm-variable.tok-variable-name
.cm-def.tok-name.tok-definition, .tok-variable-name.tok-function
.cm-type.tok-type-name, .tok-class-name
.cm-operator.tok-operator
.cm-tag.tok-tag-name
.cm-attribute.tok-attribute-name
.cm-atom.tok-atom, .tok-bool
.cm-meta.tok-meta

Before:

.cm-s-default {
  .cm-keyword {
    color: #c678dd;
  }
  .cm-string {
    color: #98c379;
  }
}

After:

.cm-editor,
.mde-preview .codeblock {
  .tok-keyword {
    color: #c678dd;
  }
  .tok-string {
    color: #98c379;
  }
}

Inkdrop ships a primitive color palette — Tailwind-style --hsl-<family>-<scale> HSL triplets — in the shared @inkdropapp/css package, loaded before your theme. Instead of hard-coding hsl(...) values, reference these tokens so your theme stays consistent with the rest of the app:

:root {
  --editor-background-color: hsl(var(--hsl-white));
  --editor-foreground-color: hsl(var(--hsl-slate-800));
  --editor-selection-background: hsl(var(--hsl-slate-500) / 40%);
}

The full list of primitive tokens is defined in tokens.css.

Preview theme

Preview themes (packages with "theme": "preview" in package.json) style the rendered Markdown preview — headings, body text, links, blockquotes, tables, code blocks, task lists, GFM alerts, and so on. This is the theme type that changed the most between v5 and v6.

In v5 a preview theme shipped the entire preview stylesheet: a fork of GitHub's github-markdown.css (usually with a separate github-markdown-dark.css for the dark UI), plus extra files such as gfm-alerts.css and metadata-section.css, often authored in LESS. Every rule hard-coded its own colors, so recoloring the preview meant editing hundreds of lines of CSS — and the theme had to be kept in sync with each change Inkdrop made to the preview markup.

In v6 those base styles are embedded in the app. Inkdrop ships them in @inkdropapp/css's markdown.css, loaded inside an @layer theme.preview.base, so a preview theme no longer carries the full stylesheet — it only overrides CSS variables. The stock default-preview theme is now essentially empty:

/* styles/index.css */
/* See https://github.com/inkdropapp/css/blob/main/markdown.css */

The base stylesheet drives every element through a family of --mde-preview-* variables, most of which default to the active UI theme's tokens (--text-color, --border-color, --link-color, …). So in v6 a preview theme matches the UI theme out of the box, and you set only the handful of --mde-preview-* variables you want to diverge on.

The stock default-preview and the Solarized Light / Solarized Dark preview themes have been updated for v6 and make good references.

Update the manifest and tooling

Bump the engine, collapse the multiple stylesheets into one, and — like every other plugin — drop LESS for plain CSS:

Before:

{
  "theme": "preview",
  "styleSheets": [
    "github-markdown.css",
    "github-markdown-dark.css",
    "gfm-alerts.css",
    "metadata-section.css"
  ],
  "engines": {
    "inkdrop": "^5.x"
  }
}

After:

{
  "theme": "preview",
  "styleSheets": ["index.css"],
  "engines": {
    "inkdrop": "^6.0.0"
  }
}

The theme: "preview" field is unchanged. The old gfm-alerts.css and metadata-section.css are now part of the embedded base, so you can drop them. Preview themes have no themeAppearance and — unlike UI themes — don't ship a palette.json.

Ship full stylesheets → override variables

This is the breaking change. Delete your forked GitHub-markdown rules and replace them with variable overrides scoped to .mde-preview. Anything you don't override falls back to the app's base styles (which track the active UI theme).

Before (v5 — styling elements directly):

.mde-preview {
  color: #333;
}
.mde-preview a {
  color: #4078c0;
}
.mde-preview code {
  background-color: rgba(27, 31, 35, 0.05);
}
.mde-preview blockquote {
  color: #777;
  border-left: 0.25em solid #ddd;
}

After (v6 — overriding variables):

.mde-preview {
  --mde-preview-link-color: hsl(var(--hsl-blue-500));
  --mde-preview-inline-code-background-color: hsl(var(--hsl-slate-100));
  --mde-preview-blockquote-text-color: hsl(var(--hsl-slate-500));
  --mde-preview-blockquote-border-color: hsl(var(--hsl-slate-200));
}

Reach for the shared @inkdropapp/css palette (--hsl-<family>-<scale> / --color-<family>-<scale>) for values, the same way syntax and UI themes do.

For the few things the variables don't cover — coloring strong, em, or the headings, for example — you can still write ordinary rules inside .mde-preview:

.mde-preview {
  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
    color: var(--color-yellow-500);
  }
  em {
    color: var(--color-green-500);
  }
}

The variables

The full list lives in the :root block of markdown.css. The most commonly themed ones:

AreaVariable(s)
Links--mde-preview-link-color
Headings--mde-preview-heading-border-color, --mde-preview-heading-muted-text-color
Blockquotes--mde-preview-blockquote-text-color, --mde-preview-blockquote-border-color
Inline code--mde-preview-inline-code-text-color, --mde-preview-inline-code-background-color, --mde-preview-inline-code-border-color / -border-width
Code blocks--mde-preview-codeblock-background-color, --mde-preview-codeblock-border-color, --mde-preview-codeblock-text-color, --mde-preview-codeblock-meta-*
Tables--mde-preview-table-head-*, --mde-preview-table-border-color, --mde-preview-table-row-*
Highlight (==mark==)--mde-preview-inline-mark-text-color, --mde-preview-inline-mark-background-color, --mde-preview-inline-mark-underline-color
Task lists--mde-preview-task-list-checked-color
Footnotes--mde-preview-footnote-*
Keyboard keys--mde-preview-kbd-*
GFM alerts--gfm-alert-note, --gfm-alert-tip, --gfm-alert-important, --gfm-alert-warning, --gfm-alert-caution

Code blocks: frame vs. tokens

A preview theme styles the code block frame — background, border, and the meta/title bar — through the --mde-preview-codeblock-* variables above. The syntax highlighting inside the block (the .tok-* classes under .mde-preview .codeblock) belongs to the syntax theme, not the preview theme. See Syntax themes for that side.

See Creating a Preview Theme for the full dev loop (ipm init --type theme-preview, ipm link --dev, and hot reloading).

Can you help us improve the docs? 🙏

The source of these docs is here on GitHub. If you see a way these docs can be improved, please fork us!