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!')

Themes are now a single, unified type

In Inkdrop 5, a visual theme was up to three separate packages — a UI theme, a syntax theme, and a preview theme — each declaring "theme": "ui" | "syntax" | "preview" in its package.json and selected independently from three drop-downs.

Inkdrop 6 merges them into one theme package:

  • package.json sets "theme": true — a boolean, not a string.
  • It ships up to three stylesheets — ui.css, syntax.css, preview.css — one per area (app chrome, editor, preview), each wrapped in its own cascade layer.
  • A single themeAppearance: "light" | "dark" describes the whole theme.
  • Preferences → Themes shows one Theme drop-down instead of three.

Before (three packages):

{
  "theme": "ui",
  "themeAppearance": "light",
  "styleSheets": ["theme.css"]
}

After (one package):

{
  "theme": true,
  "themeAppearance": "light",
  "styleSheets": ["ui.css", "syntax.css", "preview.css"]
}

Wrap each stylesheet in its cascade layer

The app no longer wraps your theme CSS in an @layer for you — your stylesheets are now injected verbatim. Wrap each one in the matching layer (theme.ui, theme.syntax, or theme.preview) so its overrides land in the right place in the cascade:

@layer theme.ui {
  :root {
    --page-background: var(--color-neutral-50);
    --sidebar-background: var(--color-neutral-100);
  }
}

The app's built-in defaults live in the theme.ui.base / theme.ui.components, theme.syntax.base, and theme.preview.base sub-layers, and a layer's own rules outrank its sub-layers. So wrapping your overrides in @layer theme.ui (etc.) lets them win over the defaults reliably, without resorting to high-specificity selectors.

Merging three v5 themes into one

If you maintained separate -ui, -syntax, and -preview packages, combine them into a single repository:

  1. Keep one package.json with "theme": true, a single themeAppearance, and a styleSheets array.
  2. Move each old package's CSS into ui.css, syntax.css, and preview.css respectively, each wrapped in its @layer.
  3. If the three packages shared a color palette, keep those raw tokens in one shared file (e.g. palette.css, wrapped in @layer theme) listed first in styleSheets, and reference them from the three layer files via var(). Don't copy the color definitions into all three — define them once. See Sharing a palette across the three stylesheets.
  4. List the files in styleSheets (tokens first, then the layered stylesheets).

The official Solarized and Kanagawa themes were migrated exactly this way 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:

{
  "theme": "ui",
  "styleSheets": ["theme.css"],
  "devDependencies": {
    "@inkdropapp/theme-dev-helpers": "^0.3.6"
  },
  "engines": {
    "inkdrop": "^5.9.0"
  }
}

After:

{
  "theme": true,
  "styleSheets": ["ui.css", "syntax.css", "preview.css"],
  "scripts": {
    "prepublishOnly": "generate-palette"
  },
  "devDependencies": {
    "@inkdropapp/theme-dev-helpers": "^0.6.0"
  },
  "engines": {
    "inkdrop": "^6.0.0"
  }
}

Like every other plugin, themes also drop LESS for plain CSS.

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.

Generate palette.json

All 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. The prepublishOnly script above runs the generate-palette binary from @inkdropapp/theme-dev-helpers on every publish, so you don't need to generate or commit the file by hand. To inspect it beforehand, run it yourself:

npx generate-palette

See Creating a theme for the full dev loop (ipm init --type theme, npx dev-server, hot reloading, and the component preview).

The UI stylesheet (ui.css)

This stylesheet styles 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.

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

Override the built-in variables

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-*)

Optional: recolor by overriding the ramps

If your theme's palette is shaped like Inkdrop's own design tokens — Tailwind-style color ramps with numeric shades — you can recolor the whole UI from one place by redefining the primitive --hsl-* / --color-* ramps in a token file wrapped in @layer theme, rather than repointing every component variable by hand. The theme authoring guide covers this in detail — see recolor by overriding the ramps.

Move preview styling out

If your v5 UI theme styled preview content directly (e.g. .mde-preview h1, .mde-preview em), move those rules into your theme's preview stylesheet instead. For any UI-level rule you keep, note that the container class was renamed .mde-preview.mde-preview-container (see above).

The editor stylesheet (syntax.css)

This stylesheet styled CodeMirror 5 in v5. Inkdrop 6 runs on CodeMirror 6, and the model shifts in two ways: the theme now (1) sets CSS variables instead of styling CodeMirror's DOM, and (2) sets --syntax-* color variables instead of writing per-token rules. The rules that paint the tokens — the CodeMirror 6 / Lezer .tok-* classes — now live in the shared @inkdropapp/css package and read your variables. So a v6 editor stylesheet is a single :root { … } block (wrapped in @layer theme.syntax) with no selectors of its own.

The base syntax theme now lives in @inkdropapp/css's syntax.css: it defines every --editor-*, --syntax-*, and --md-* variable with a default value (the built-in light and dark themes merged via light-dark()). The app loads it before your theme, so you set only the variables you want to change.

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 — with their default values — in @inkdropapp/css's syntax.css.

Syntax tokens: .cm-*--syntax-* variables

CodeMirror 5 mode tokens map to v6 highlight tokens — but instead of writing rules for them, you set one --syntax-* color variable per token. @inkdropapp/css applies each variable to the matching .tok-* class it emits under .cm-editor and .mde-preview .codeblock (the latter for rendered preview code blocks):

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

Before:

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

After:

@layer theme.syntax {
  :root {
    --syntax-keyword-color: #c678dd;
    --syntax-string-color: #98c379;
  }
}

A few conventions: each token also exposes -font-style / -font-weight / -text-decoration variants where relevant (--syntax-comment-font-style, --syntax-heading-font-weight, …); modifier variants default to their base token (--syntax-name-function-color falls back to --syntax-name-color); and any token you don't color falls back to --editor-foreground-color. @inkdropapp/css's syntax.css defines a value for every token — copy it as a starting point.

The preview stylesheet (preview.css)

This stylesheet styles the rendered Markdown preview — headings, body text, links, blockquotes, tables, code blocks, task lists, GFM alerts, and so on. This is the area 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 your preview stylesheet no longer carries the full stylesheet — it only overrides CSS variables.

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

Ship full stylesheets → override variables

This is the breaking change. Delete your forked GitHub-markdown rules and replace them with variable overrides on :root, inside @layer theme.preview. Anything you don't override falls back to the app's base styles (which track the rest of your 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):

@layer theme.preview {
  :root {
    --mde-preview-link-color: hsl(var(--hsl-blue-500));
    --mde-preview-heading-color: hsl(var(--hsl-slate-900));
    --mde-preview-strong-color: hsl(var(--hsl-slate-900));
    --mde-preview-em-color: hsl(var(--hsl-slate-700));
    --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 the UI and editor stylesheets do.

For non-color tweaks that no variable covers — font weight, letter-spacing, and the like — you can still write ordinary rules under .mde-preview:

@layer theme.preview {
  .mde-preview {
    strong {
      font-weight: 800;
    }
    h1,
    h2,
    h3,
    h4,
    h5,
    h6 {
      letter-spacing: -0.02em;
    }
  }
}

The old gfm-alerts.css and metadata-section.css are now part of the embedded base, so you can drop them.

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-color, --mde-preview-heading-border-color, --mde-preview-heading-muted-text-color
Strong / emphasis--mde-preview-strong-color, --mde-preview-em-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

The preview stylesheet 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) is driven by your theme's editor stylesheet, not the preview stylesheet.

Mermaid diagram colors

In v5, diagrams rendered by the mermaid plugin had their own theme / themeCSS / themeVariables plugin settings, separate from your theme's stylesheet — matching a diagram's colors to your theme meant configuring the plugin on top of it, if at all.

That plugin setting is gone in v6. Diagrams now always render with a built-in theme driven by CSS variables: @inkdropapp/css's mermaid.css defines a --mermaid-* token for every color Mermaid can draw, layered the same way as the rest of the preview — you override the ones you care about in preview.css, and everything else falls back to defaults that already track --text-color, --border-color, and the rest of your palette. See Diagram colors (Mermaid) in the theme guide for the token list and an example.

If your v5 theme shipped custom Mermaid CSS or documented the plugin's theme option for users, drop it — it no longer applies, and the --mermaid-* variables replace it.

See Creating a theme for the full dev loop (ipm init --type theme, 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!