# Cortex Documentation
LLM-readable documentation corpus for Cortex.
Canonical index: https://cortex-md.tech/llms.txt
# Welcome
Start with the Cortex model: local Markdown, a native workspace, optional encrypted sync, and extension points that keep files portable.
Source: https://cortex-md.tech/docs/get-started/welcome
Section: Get Started
Cortex is a local-first Markdown workspace. It opens a folder you control, keeps notes as plain `.md` files, and gives the writing surface enough structure to stay fast without taking ownership of the format.
## Start here
Use the first pass through the docs to understand three things: where your notes live, how Cortex reads Markdown, and which product surfaces are ready for extension work.
## What Cortex is
Cortex is designed for people who already trust Markdown but still want a calmer workspace for reading, writing, searching, and arranging notes. The app keeps the local folder as the source of truth, so the same files remain usable in other editors.
## What is still growing
The public documentation starts with the product model, workspace basics, sync architecture, and developer surfaces. Public download, license, platform release, and registry details should only be documented when the repository adds those contracts.
## Product boundaries
Sync, plugins, themes, and the CLI are separate stories. Sync is about ownership, client-side encryption, history, and self-hosting. Plugins extend workflows through reviewed APIs. Themes change the app surface through CSS without making notes less portable.
---
# Quickstart
Open a local Markdown folder and move through the core Cortex workflow without importing or converting notes.
Source: https://cortex-md.tech/docs/get-started/quickstart
Section: Get Started
The fastest way to understand Cortex is to open an existing Markdown folder and follow the file from disk, through the editor, and back out again.
## Open a folder
Choose a folder that already contains Markdown files, or create a small vault for testing. Cortex reads the folder directly instead of copying notes into a proprietary workspace.
```sh title="Open an existing notes folder"
~/notes
├── inbox.md
├── projects
│ └── cortex.md
└── reading
└── markdown.md
```
## Write in Live Preview
Live Preview keeps Markdown readable while you type. Headings, links, callouts, code, and task lists render close to how they will read, but the source stays plain Markdown.
```md meta="future-hints"
## Meeting notes
- [ ] Confirm sync recovery flow
- [x] Keep the source file readable
> [!note]
> This callout remains Markdown.
```
## Navigate without rebuilding your system
Use search, folders, tags, bookmarks, tabs, and split panes together. Cortex should help you move through the vault you already have, not force a new hierarchy before you can write.
## Next steps
---
# Local Markdown
Cortex keeps Markdown files on disk as the source of truth, so notes stay readable with or without the app.
Source: https://cortex-md.tech/docs/workspace/local-markdown
Section: Workspace
Cortex treats the folder on your computer as the durable workspace. The app adds navigation, search, preview, and extension surfaces around the files instead of replacing them.
## Source of truth
The file on disk is the canonical note. You can open it in another editor, back it up, version it with Git, or move it into another folder without asking Cortex for an export.
```md
# Research note
Tags: #writing #local-first
The note is still text. Cortex adds the workspace around it.
```
## Markdown compatibility
The renderer supports common Markdown patterns used in technical notes: headings, lists, links, tables, code blocks, task lists, callouts, and wiki-style references where the app can resolve them.
## Metadata stays inspectable
Properties and tags should remain understandable from the file. Cortex can make metadata easier to scan, but the content should still make sense in plain text.
## Related docs
---
# Organizing Notes
Use folders, tags, bookmarks, and properties together while keeping the workspace readable as files.
Source: https://cortex-md.tech/docs/workspace/organizing
Section: Workspace
Organization in Cortex is meant to be layered. A folder can hold the broad shape, tags can connect ideas, bookmarks can keep active notes close, and properties can record lightweight state.
## Folders
Folders are the structure that survives everywhere. Use them for areas, projects, archives, and anything that benefits from being visible in the file system.
## Tags
Tags are lightweight relationships. They are useful when a note belongs to more than one context, or when a future search should collect notes across folders.
```md
---
status: draft
area: product
---
# Plugin API notes
#plugin #markdown #review
```
## Bookmarks
Bookmarks should be temporary and practical. They are for notes you need to reach often while a project is active, not a second permanent taxonomy.
## Properties
Properties make repeated note shapes easier to scan. Keep them simple enough that the raw file still communicates the same thing.
---
# Sync Overview
Cortex Sync is optional and built around client-side encryption with encrypted blob storage.
Source: https://cortex-md.tech/docs/sync/overview
Section: Sync
Cortex Sync is an optional layer for moving notes between devices while preserving the local folder model. The service should not become the source of truth for your notes.
## Ownership model
Markdown files stay on your devices. The sync service is responsible for encrypted blob storage, transfer, version history, and conflict recovery around those local files.
## Client-side encryption
Notes are encrypted on the client before upload. The server stores encrypted blobs and the metadata needed to coordinate sync, rather than readable note contents.
```ts title="Conceptual flow"
const localChange = await readMarkdownFile(path)
const encryptedBlob = await encryptOnDevice(localChange, deviceKey)
await syncClient.upload({
vaultId,
path,
blob: encryptedBlob,
})
```
## History and conflicts
History should make changes understandable across devices. When conflicts happen, Cortex should preserve enough context for recovery instead of silently overwriting local work.
## Self-hosting path
The sync architecture should support a hosted service and a self-hostable path. Documentation should avoid absolute security promises and describe the actual boundaries clearly.
## Related docs
---
# Community Plugins
Build reviewed, vault-scoped Cortex plugins with the public API while keeping notes portable.
Source: https://cortex-md.tech/docs/plugins/overview
Section: Plugin Development
Community plugins extend Cortex through the public `@cortex.md/api` package. The host creates a
plugin instance, injects `this.manifest` and `this.api`, calls `onload()`, and disposes tracked
registrations when the plugin unloads.
## Extension surfaces
Plugins can add commands, editor behavior, Markdown rendering, property types, portable views,
settings, sidebar entries, status bar items, context menus, vault-aware workflows, and plugin-owned
data. Each surface is guarded by explicit capabilities so extension behavior can be reviewed before
listing.
## Install location
During development, place or symlink a plugin directory here:
```text
/.cortex/plugins//
manifest.json
dist/index.js
styles.css # optional, Markdown-surface styles only
```
Cortex watches the vault plugin directory and reloads enabled community plugins when plugin files
change.
## Start here
## Rules of thumb
- Import only from `@cortex.md/api` and your own bundled code.
- Declare every required capability in `manifest.json`.
- Use vault-relative paths with forward slashes.
- Use declarative views instead of React or DOM APIs.
- Keep view state serializable and update it through actions and reducers.
- Dispose manual timers, external subscriptions, and caches in `onunload()`.
## Deeper references
---
# Getting Started With Plugins
Create a small command plugin, build it, and load it from a local Cortex vault.
Source: https://cortex-md.tech/docs/plugins/getting-started
Section: Plugin Development
This guide creates a small command plugin that inserts text into the active editor.
## Minimal Folder
```text
hello-cortex/
manifest.json
package.json
src/index.ts
```
## Manifest
```json
{
"id": "hello-cortex",
"name": "Hello Cortex",
"version": "0.1.0",
"minAppVersion": "0.1.0",
"author": "Your Name",
"description": "Adds a sample command.",
"icon": "sparkles",
"main": "dist/index.js",
"capabilities": ["commands", "editor:write"]
}
```
`main` must be a safe relative path inside the plugin directory. Absolute paths, drive-letter paths,
empty paths, and paths containing `..` are rejected.
## Package
```json
{
"name": "hello-cortex",
"version": "0.1.0",
"type": "module",
"scripts": {
"build": "bun build ./src/index.ts --outfile ./dist/index.js --target browser --format cjs --external @cortex.md/api"
},
"dependencies": {
"@cortex.md/api": "^0.1.0"
},
"devDependencies": {
"typescript": "~5.8.3"
}
}
```
The desktop host can load CommonJS bundles through a host-provided `@cortex.md/api` external. ESM
bundles are also supported when the final bundle is self-contained and exports a default plugin
class.
## Plugin Entry
```ts
import { CortexPlugin } from "@cortex.md/api"
export default class HelloCortexPlugin extends CortexPlugin {
onload(): void {
this.addCommand({
id: "insert-hello",
label: "Insert Hello",
icon: "sparkles",
defaultHotkey: "mod+shift+h",
execute: () => {
this.api.editor.insertAtCursor("Hello from Cortex")
},
})
}
}
```
## Build Output
After `bun run build`, copy or symlink the plugin into the active vault:
```text
/.cortex/plugins/hello-cortex/
manifest.json
dist/index.js
```
Keep `manifest.json` at the plugin root. Keep the built file at the path declared by `main`.
## Manual development link
When you are not using the CLI, the manual development workflow is:
```bash
mkdir -p "/.cortex/plugins"
ln -s "/absolute/path/to/hello-cortex" "/.cortex/plugins/hello-cortex"
```
Restart Cortex or edit a plugin file to trigger the plugin watcher. The plugin list should show the
manifest name and icon if the bundle loads successfully.
## Next steps
---
# Manifest and Capabilities
Define plugin identity, bundle paths, and explicit capabilities for reviewed extension behavior.
Source: https://cortex-md.tech/docs/plugins/manifest-and-capabilities
Section: Plugin Development
Every community plugin needs a `manifest.json` at the plugin root.
## Manifest Shape
```json
{
"id": "example-plugin",
"name": "Example Plugin",
"version": "0.1.0",
"minAppVersion": "0.1.0",
"author": "Your Name",
"authorUrl": "https://example.com",
"description": "Adds a sample command and sidebar view.",
"icon": "puzzle",
"main": "dist/index.js",
"capabilities": ["commands", "ui:views", "ui:sidebar"]
}
```
| Field | Required | Notes |
| --- | --- | --- |
| `id` | Yes | Stable id unique inside a vault. Reviewed listings must match this id. |
| `name` | Yes | User-facing name. |
| `version` | Yes | Use semver for update comparisons. |
| `minAppVersion` | Yes | Minimum Cortex version the plugin expects. |
| `author` | Yes | Displayed in plugin and reviewed listing surfaces. |
| `authorUrl` | No | Public URL for the author or organization. |
| `description` | Yes | Short install/review summary. |
| `icon` | Yes | Lucide icon name or host-supported icon identifier. |
| `main` | Yes | Safe relative path to the plugin bundle. |
| `capabilities` | No | Required before guarded API calls work. |
## Capability Reference
| Capability | Enables |
| --- | --- |
| `commands` | `api.commands.register`, `api.commands.execute`, command palette, hotkeys, Vim names. |
| `settings` | Plugin settings storage, schemas, settings tabs, and `onChange` listeners. |
| `vault:read` | Vault path, file reads, file listings, `exists`, metadata reads, and tag reads. |
| `vault:write` | Writing text files in the active vault. |
| `vault:delete` | Deleting files from the active vault. |
| `vault:watch` | Subscribing to vault file events. |
| `editor:read` | Active file path and active editor content reads. |
| `editor:write` | Cursor insertion and selection replacement. |
| `editor:extensions` | Host-specific editor extensions. |
| `editor:folding` | Portable line-based fold providers. |
| `markdown:extensions` | Inline, semantic, callout, preprocessor, processor, and plugin `styles.css` support. |
| `properties:types` | Custom note property type registration. |
| `ui:views` | Declarative host-rendered views. |
| `ui:sidebar` | Sidebar items that open registered views. |
| `ui:statusbar` | Status bar items. |
| `ui:contextmenu` | File, editor, and tab context menu items. |
| `ui:modals` | Opening and closing registered modal views. |
| `workspace:tabs` | Opening files, views, and temporary Markdown tabs. |
| `theme:read` | Active theme name and theme change subscriptions. |
| `bookmarks:read` | Bookmark list, lookup, and change subscriptions. |
| `bookmarks:write` | Add, remove, and toggle bookmarks. |
| `data` | Plugin-owned data files. |
| `notifications` | Native notifications and lightweight notices. |
## Best Practices
- Request the narrowest capabilities you need.
- Treat capabilities as user-visible permissions.
- Do not call APIs defensively and ignore capability failures. Declare the capability or remove the
feature.
- Use local ids for plugin commands, views, settings, and context menu items. Cortex prefixes where
global uniqueness is needed.
---
# Lifecycle and Bundling
Export a Cortex plugin class, bundle safely, and clean up registrations when the plugin unloads.
Source: https://cortex-md.tech/docs/plugins/lifecycle-and-bundling
Section: Plugin Development
Plugins extend `CortexPlugin` from `@cortex.md/api`.
## Lifecycle
```ts
import { CortexPlugin, type Disposable } from "@cortex.md/api"
export default class WatchPlugin extends CortexPlugin {
private watcher?: Disposable
private timer?: ReturnType
onload(): void {
this.watcher = this.api.vault.onFileEvent((event) => {
console.log("Vault changed", event.path)
})
this.timer = setInterval(() => {
console.log("Still alive")
}, 60_000)
}
onunload(): void {
this.watcher?.dispose()
if (this.timer) clearInterval(this.timer)
}
}
```
Helper methods such as `addCommand`, `registerView`, `registerSettingsTab`, and
`registerMarkdownInline` track disposables automatically. Use `onunload()` for things Cortex cannot
track for you, such as timers, in-memory caches, network subscriptions, and external resources.
## Bundle Contract
The plugin bundle must export a default plugin class:
```ts
export default class MyPlugin extends CortexPlugin {
onload(): void {}
}
```
The desktop host loads:
- CommonJS bundles through a controlled `require` stub.
- ESM bundles through a generated `data:` URL import when the bundle looks like ESM.
For the most predictable development setup, emit one CommonJS browser-targeted bundle and keep
`@cortex.md/api` external.
## Imports
Allowed:
```ts
import { CortexPlugin, type ViewDescriptor } from "@cortex.md/api"
import { helper } from "./helper"
```
Avoid:
```ts
import { useVaultStore } from "@cortex/core"
import { EditorView } from "@cortex/editor/editor-view"
import { getPlatform } from "@cortex/platform"
```
Community plugins must not import Cortex internals. Public APIs are exposed through `this.api`.
## Dependency Guidance
- Bundle third-party runtime dependencies into your plugin output.
- Keep large dependencies lazy inside your own code when the feature is rarely used.
- Avoid global side effects at module top level. Register behavior in `onload()`.
- Keep plugin state serializable when it flows through views, settings, or workspace tabs.
---
# Commands and Hotkeys
Register command palette actions, default hotkeys, and Vim command names through the host registry.
Source: https://cortex-md.tech/docs/plugins/commands-and-hotkeys
Section: Plugin Development
Plugin commands enter the same command registry used by Cortex's command palette, configurable
hotkeys, menus, and Vim command-line choices.
## Register a Command
```ts
this.addCommand({
id: "open-dashboard",
label: "Open Dashboard",
category: "Dashboard",
aliases: ["stats", "overview"],
icon: "layout-dashboard",
defaultHotkey: "mod+shift+d",
execute: () => {
this.api.workspace.openView("dashboard")
},
})
```
Declare `commands` in `manifest.json`.
## Command Ids
Public command ids are local to the plugin. Cortex prefixes them internally before they enter the
global command registry, so `open-dashboard` can safely exist in more than one plugin.
Use stable command ids because users may assign custom hotkeys to them.
## Hotkeys
Use `defaultHotkey` for the suggested shortcut:
```ts
defaultHotkey: "mod+shift+d"
```
The user can reassign or disable the binding in Cortex. Do not implement a second hotkey system in
your plugin.
## Vim Names
Cortex derives Vim command-line choices from the same command registry. Keep labels and ids clear
because Vim names are generated from the command id and shown to users in command hints.
## Execution Guidelines
- Keep command handlers small and responsive.
- For longer work, show progress through a view, status bar item, notification, or generated
Markdown tab.
- Use `api.commands.execute(id)` only for commands your plugin owns or intentionally composes with.
---
# Vault, Editor, and Workspace APIs
Use vault files, the active editor, fold providers, and workspace tabs from a plugin.
Source: https://cortex-md.tech/docs/plugins/vault-editor-workspace
Section: Plugin Development
Use these APIs when a plugin needs user files, the active editor, or workspace tabs.
## Vault Paths
Vault APIs use vault-relative paths with forward slashes:
```ts
const content = await this.api.vault.readFile("Projects/Plan.md")
await this.api.vault.writeFile("Projects/Plan.md", `${content}\n\nUpdated by plugin.`)
```
Do not pass absolute paths, `..`, or platform-specific separators. The host validates paths before
touching the filesystem.
## Vault Events
```ts
const disposable = this.api.vault.onFileEvent((event) => {
if (event.path.endsWith(".md")) {
console.log(event.type, event.path)
}
})
```
Declare `vault:watch`. Dispose subscriptions you create manually.
## Editor Reads and Writes
```ts
const filePath = this.api.editor.getActiveFilePath()
const content = this.api.editor.getActiveFileContent()
if (filePath && content !== null) {
this.api.editor.replaceSelection("**important**")
}
```
Use `editor:read` for reads and `editor:write` for writes. Editor paths are still vault-relative
when available.
## Fold Providers
Fold providers are portable and line-based. They do not expose CodeMirror or DOM objects.
```ts
this.registerFoldProvider({
id: "spoiler-blocks",
label: "Spoiler blocks",
getFoldRange: (context) => {
if (context.lineText !== ":::spoiler") return null
for (let line = context.lineNumber + 1; line <= context.lineCount; line += 1) {
if (context.getLine(line) === ":::") return { toLine: line, placeholder: "spoiler" }
}
return null
},
})
```
Declare `editor:folding`.
## Workspace Tabs
Open vault files:
```ts
this.api.workspace.openFile("Projects/Plan.md", { target: "right", newTab: true })
```
Open a registered plugin view:
```ts
this.api.workspace.openView("dashboard", { target: "active" })
```
Open generated Markdown without writing a file:
```ts
this.openMarkdownTab({
title: "Plugin Report",
content: "# Report\n\nGenerated by my plugin.",
})
```
Workspace open methods require `workspace:tabs`. Opening plugin views and generated Markdown tabs
also requires `ui:views`.
---
# Markdown and Properties
Extend Markdown rendering, callouts, plugin-scoped styles, and note property types.
Source: https://cortex-md.tech/docs/plugins/markdown-and-properties
Section: Plugin Development
Markdown extensions require the `markdown:extensions` capability. Property type extensions require
`properties:types`.
## Inline Replacements
Use inline registrations for simple regex replacements:
```ts
this.registerMarkdownInline({
id: "smile-shortcode",
pattern: ":smile:",
replacement: { type: "text", content: "smile" },
})
```
Inline registrations are lightweight and shared by editor and renderer consumers.
## Semantic Transforms
Use semantic registrations when output should work across Live Preview, Reading View, and export.
Semantic output must be portable nodes, not arbitrary DOM.
```ts
this.registerMarkdownSemantic({
id: "ticket-links",
selector: { type: "text" },
priority: 10,
transform: ({ node }) => {
if (node.type !== "text" || !node.value.includes("APP-")) return null
return node
},
})
```
Supported portable nodes include text, container, span, link, image, and code.
## Callout Types
```ts
this.registerCalloutType({
type: "decision",
aliases: ["decide"],
label: "Decision",
color: "var(--accent)",
backgroundColor: "var(--accent-subtle)",
})
```
Later registrations win. Disposing a registration restores the previous definition.
## Preprocessors and Unified Processors
Preprocessors and Unified processors run only on `reading-view` and `export` surfaces:
```ts
this.registerMarkdownPreprocessor({
id: "append-footer",
surfaces: ["reading-view"],
preprocess: (markdown) => `${markdown}\n\n---\nRendered by plugin.`,
})
```
Live Preview integrations should use inline registrations, semantic registrations, callouts, fold
providers, or editor extensions instead.
## Plugin `styles.css`
A plugin can include a root `styles.css` file only when it declares `markdown:extensions`.
Plugin CSS is scoped to `.markdown-surface`. Selectors that do not already target
`.markdown-surface` are prefixed by the host. Selectors that escape the Markdown surface with
sibling combinators are rejected.
Allowed at-rules:
- `@container`
- `@layer`
- `@media`
- `@supports`
Blocked at-rules include `@font-face`, `@import`, `@keyframes`, `@namespace`, and `@page`.
## Property Types
```ts
this.registerPropertyType({
type: "rating",
baseType: "number",
displayName: "Rating",
icon: "star",
deserialize: (value) => Number(value ?? 0),
serialize: (value) => Number(value ?? 0),
validate: (value) => ({
valid: typeof value === "number" && value >= 0 && value <= 5,
message: "Rating must be between 0 and 5",
}),
})
```
Property type ids are namespaced internally by plugin id.
---
# Views, Settings, and UI
Build portable host-rendered plugin views, settings, sidebar items, status items, and menus.
Source: https://cortex-md.tech/docs/plugins/views-settings-and-ui
Section: Plugin Development
Plugins render UI through portable descriptors. Cortex owns the actual React/web rendering so plugin
UI can remain portable across hosts.
## Register a View
```ts
import type { ViewDescriptor, ViewDispatch, ViewState } from "@cortex.md/api"
function renderCounter(state: ViewState, _dispatch: ViewDispatch): ViewDescriptor {
const count = Number(state.state.count ?? 0)
return {
type: "stack",
gap: "sm",
children: [
{ type: "heading", value: "Counter" },
{ type: "text", value: `Count: ${count}` },
{ type: "button", label: "Increment", action: "increment", variant: "primary" },
],
}
}
this.registerView({
id: "counter",
label: "Counter",
icon: "plus",
location: "sidebar-left",
initialState: { count: 0 },
reduce: (state, action) => {
if (action === "increment") return { ...state, count: Number(state.count ?? 0) + 1 }
return state
},
render: renderCounter,
})
```
Declare `ui:views`.
## View Locations
Views can render in:
- `tab`
- `sidebar-left`
- `sidebar-right`
- `modal`
Modal views also require `ui:modals`.
## Sidebar, Status Bar, and Context Menus
```ts
this.registerSidebarItem({
id: "counter",
label: "Counter",
icon: "plus",
viewId: "counter",
})
this.registerStatusBarItem({
id: "counter-status",
position: "right",
icon: "plus",
text: "Counter",
tooltip: "Open counter",
onClick: () => this.api.workspace.openView("counter"),
})
this.registerContextMenuItem({
id: "copy-path",
label: "Copy Path",
location: "file",
action: async (context) => {
if (context.location === "file") await this.api.data.write("last-path.txt", context.filePath)
},
})
```
Required capabilities are `ui:sidebar`, `ui:statusbar`, and `ui:contextmenu`.
## Settings Tabs
```ts
this.registerSettingsTab({
id: "counter-settings",
label: "Counter",
icon: "settings",
settings: [
{
key: "enabled",
label: "Enabled",
type: "boolean",
default: true,
},
],
})
```
Supported setting field types are `text`, `number`, `boolean`, `select`, `slider`, `color`, and
`folder-path`.
## Portable UI Nodes
Use the descriptor nodes exposed by `@cortex.md/api`: stack, row, text, heading, button,
icon-button, input, textarea, toggle, checkbox, select, slider, icon, separator, list, list-item,
scroll-area, badge, progress, empty, markdown, setting-row, item, alert, tabs, and table.
Do not expose React components, DOM nodes, inline styles, arbitrary event handlers, or free
`className` values.
---
# Storage, Bookmarks, and Notifications
Store plugin-owned data and integrate with bookmarks, metadata, theme reads, notices, and notifications.
Source: https://cortex-md.tech/docs/plugins/storage-bookmarks-notifications
Section: Plugin Development
These APIs integrate plugins with host-owned data, user bookmarks, metadata, themes, and native
notifications.
## Plugin Data
Plugin data is vault-scoped and stored under the plugin directory:
```text
/.cortex/plugins//data/
```
```ts
await this.api.data.write("cache.json", JSON.stringify({ lastRun: Date.now() }))
const cached = await this.api.data.read("cache.json")
await this.api.data.delete("cache.json")
```
Declare `data`. Filenames must not contain `..` and must not start with `/` or `\`.
Use data files for plugin-owned state and caches. Use vault APIs for user-visible notes.
## Metadata
Metadata APIs require `vault:read`:
```ts
const frontmatter = await this.api.metadata.getFrontmatter("Projects/Plan.md")
const tags = await this.api.metadata.getTags("Projects/Plan.md")
const allTags = this.api.metadata.getAllTags()
```
Metadata paths are vault-relative Markdown paths.
## Bookmarks
```ts
const bookmarks = this.api.bookmarks.list()
await this.api.bookmarks.toggle("Projects/Plan.md")
const isBookmarked = this.api.bookmarks.isBookmarked("Projects/Plan.md")
```
Reads require `bookmarks:read`. Mutations require `bookmarks:write`. Bookmark paths are
vault-relative Markdown paths.
## Theme Read API
```ts
const activeTheme = this.api.theme.getActiveThemeName()
const disposable = this.api.theme.onThemeChange((name) => {
console.log("Theme changed", name)
})
```
Declare `theme:read`. Plugins can observe the active theme name, but they should not mutate themes.
## Notifications and Notices
```ts
const result = await this.notify({
title: "Task complete",
body: "Your plugin finished processing notes.",
kind: "success",
})
if (!result.delivered) {
console.warn(result.reason)
}
```
Declare `notifications`. Plugins cannot request OS notification permission directly; Cortex owns
permission prompts and platform support.
`api.ui.showNotice(message)` also requires `notifications` and is delivered through the host notice
path.
Notifications are rate-limited per plugin. Keep them user-meaningful and avoid sending repeated
status spam.
---
# Publishing and Review
Prepare plugin release assets for review and community listing without promising an unreviewed install path.
Source: https://cortex-md.tech/docs/plugins/publishing-and-review
Section: Plugin Development
Reviewed community listing is release-asset based with a source-archive fallback during early
development.
## Required Release Assets
Publish these assets in the latest release:
```text
manifest.json
dist/index.js # or the basename/path referenced by manifest.main
styles.css # optional, Markdown-surface styles only
```
Cortex downloads `manifest.json`, reads `main`, then downloads the matching main asset. It accepts
either the exact relative path from `main` or the basename of that path.
## Manifest checks
The reviewed listing flow verifies:
- `manifest.json` is valid JSON.
- `manifest.id` matches the listing entry id.
- `manifest.main` is present and safe.
- The main bundle exists in release assets or in the source archive fallback.
## Source Archive Fallback
If release assets are incomplete, Cortex can fall back to the GitHub source archive. It searches for
`manifest.json`, resolves the declared `main` file, and copies optional `styles.css` into staging.
This fallback is useful during early development, but reviewed plugins should still include release
assets explicitly.
## Install and rollback behavior
Cortex installs into:
```text
/.cortex/plugins/
```
Install and update use a staging directory. The previous plugin directory is preserved until the
staged plugin is promoted, the plugin host reloads, and the new plugin registration is visible. If
anything fails, Cortex removes staging and restores the previous plugin best effort.
## Release checklist
1. Build a single plugin bundle.
2. Include `manifest.json` at the release root.
3. Include the file referenced by `manifest.main`.
4. Include `styles.css` only when the plugin declares `markdown:extensions`.
5. Keep all paths relative and free of `..`.
6. Test install, reload, disable, and uninstall in a temporary vault.
---
# Example: GitHub Emoji Plugin
Study a practical plugin that registers Markdown inline replacements, commands, settings, and a portable view.
Source: https://cortex-md.tech/docs/plugins/github-emoji-example
Section: Plugin Development
The bundled GitHub emoji plugin is the best current example of a practical Cortex plugin.
Source files:
- `plugins/github-emoji/package.json`
- `plugins/github-emoji/src/index.ts`
- `plugins/github-emoji/src/emojiMap.ts`
## What it demonstrates
- Importing `CortexPlugin` from `@cortex.md/api`.
- Registering Markdown inline replacements.
- Adding command palette entries and default hotkeys.
- Writing to the active editor.
- Adding a status bar item.
- Registering a settings tab.
- Registering a declarative sidebar view with serializable state.
## Main plugin shape
```ts
import { CortexPlugin } from "@cortex.md/api"
export default class GitHubEmojiPlugin extends CortexPlugin {
onload() {
this.registerMarkdownInline({
id: "github-emoji",
pattern: ":([a-z0-9_+-]+):",
flags: "gi",
replacement: {
type: "text",
content: (match) => GITHUB_EMOJI_MAP[match[1].toLowerCase()] ?? match[0],
},
})
}
}
```
## Commands
The plugin registers commands with plugin-local ids:
- `insert-emoji`
- `emoji-reference`
The `insert-emoji` command uses `editor:write` to insert text at the cursor. The command also
declares a default hotkey, so Cortex can mirror it into the user's configurable hotkeys.
## Settings
The settings tab uses host-rendered fields:
- Boolean setting for Live Preview behavior.
- Select setting for emoji size.
- Select setting for skin tone.
- Boolean setting for status bar display.
The `onChange` handler shows a notice, so the plugin needs `notifications` if it uses that behavior
in a community manifest.
## Sidebar View
The emoji browser view is declarative. It returns portable nodes such as stack, row, scroll-area,
button, heading, and text. State changes go through view actions and a reducer instead of React
state owned by the plugin.
Use this pattern for plugin UI that should work in tabs, sidebars, and modals.
## Related docs
---
# Community Themes
Build vault-scoped Cortex themes with paired light and dark CSS files.
Source: https://cortex-md.tech/docs/themes/overview
Section: Theme Development
Community themes are vault-scoped CSS themes. Cortex discovers theme folders in the active vault,
parses each `manifest.json`, registers a light/dark theme family, and loads only the active CSS
variant on demand.
## Install location
```text
/.cortex/themes//
manifest.json
light.css
dark.css
```
## Start here
## Mental Model
- The manifest describes identity and stylesheet paths.
- The CSS files define variables and targeted rules.
- Cortex injects community CSS directly and lets the browser own the cascade.
- Theme CSS should be scoped to `body.theme--light` and
`body.theme--dark`.
## Deeper references
---
# Getting Started With Themes
Create a minimal Cortex theme with a manifest, light.css, dark.css, and vault install location.
Source: https://cortex-md.tech/docs/themes/getting-started
Section: Theme Development
This guide creates a minimal light/dark community theme.
## Folder
```text
warm-notes/
manifest.json
light.css
dark.css
```
Place or symlink it into the active vault:
```text
/.cortex/themes/warm-notes/
```
## Manifest
```json
{
"id": "warm-notes",
"name": "warm-notes",
"displayName": "Warm Notes",
"author": "Your Name",
"version": "0.1.0",
"minAppVersion": "0.1.0",
"colorschemes": {
"light": "light.css",
"dark": "dark.css"
}
}
```
`name` becomes the theme family name. Cortex registers two runtime theme names:
- `warm-notes-light`
- `warm-notes-dark`
## Light CSS
```css
body.theme-warm-notes-light {
color-scheme: light;
--bg-primary: #fbfaf6;
--bg-secondary: #f2efe8;
--bg-elevated: #ffffff;
--text-primary: #2a2621;
--text-muted: #756d62;
--accent: #b84f35;
--accent-hover: #a6462f;
--accent-subtle: #f5ded6;
--border: #ded7ca;
--modal-bg: #ffffff;
--menu-bg: #ffffff;
--markdown-content-width: 760px;
}
```
## Dark CSS
```css
body.theme-warm-notes-dark {
color-scheme: dark;
--bg-primary: #201f1d;
--bg-secondary: #2a2825;
--bg-elevated: #302e2a;
--text-primary: #f2ece2;
--text-muted: #aaa197;
--accent: #e0795d;
--accent-hover: #ee8a6e;
--accent-subtle: #4a2b25;
--border: #403b35;
--modal-bg: #2a2825;
--menu-bg: #2a2825;
--markdown-content-width: 760px;
}
```
## Reload
Cortex watches `/.cortex/themes`. Editing a theme file triggers a reload. If the active theme
is the edited family, Cortex reapplies the current appearance settings after reload.
## Next steps
---
# Theme Manifest
Define a community theme family, display metadata, and safe light and dark stylesheet paths.
Source: https://cortex-md.tech/docs/themes/manifest
Section: Theme Development
Every community theme needs a root `manifest.json`.
## Shape
```json
{
"id": "warm-notes",
"name": "warm-notes",
"displayName": "Warm Notes",
"author": "Your Name",
"authorUrl": "https://example.com",
"version": "0.1.0",
"minAppVersion": "0.1.0",
"colorschemes": {
"light": "light.css",
"dark": "dark.css"
}
}
```
| Field | Required | Notes |
| --- | --- | --- |
| `id` | Yes | Theme install id. Reviewed listings must match this id. |
| `name` | Yes | Theme family slug. Must match `^[a-z0-9][a-z0-9-]*$`. |
| `displayName` | Yes | User-facing family name. |
| `author` | Yes | Displayed in theme and reviewed listing surfaces. |
| `authorUrl` | No | Valid URL for the author or organization. |
| `version` | Yes | Use semver for update comparisons. |
| `minAppVersion` | No | Minimum Cortex version expected by the theme. |
| `colorschemes.light` | Yes | Safe relative path to the light CSS file. |
| `colorschemes.dark` | Yes | Safe relative path to the dark CSS file. |
## Safe Stylesheet Paths
Stylesheet paths must be relative to the theme folder. Cortex rejects:
- Empty paths.
- Absolute paths.
- Windows drive-letter paths.
- Paths containing `..`.
Good:
```json
{ "light": "light.css", "dark": "schemes/dark.css" }
```
Bad:
```json
{ "light": "../light.css", "dark": "/tmp/dark.css" }
```
## Runtime Theme Names
For a manifest with `"name": "warm-notes"`, Cortex registers:
- `warm-notes-light`
- `warm-notes-dark`
The active runtime theme is applied to the body as:
```html
```
Write CSS selectors against those runtime body classes.
---
# CSS Variables
Customize Cortex surfaces through documented semantic and component CSS variables.
Source: https://cortex-md.tech/docs/themes/css-variables
Section: Theme Development
Community themes customize Cortex by setting CSS variables. The variables below are generated by
the built-in theme system and are safe targets for community CSS.
## Primitive Scales
Built-in themes expose scale variables for each token key:
- `--stone-*` and `--mist-*`
- `--ink-*` and `--slate-*`
- `--amber-*` and `--rose-*`
- `--amber-d-*` and `--rose-d-*`
- `--red-*`
- `--green-*`
- `--yellow-*`
Community themes do not need to define every primitive scale. Prefer semantic variables first.
## App Surfaces
```css
--bg-primary
--bg-secondary
--bg-tertiary
--bg-elevated
--bg-hover
--bg-active
--bg-selected
--bg-code
--bg-tag
--background
--foreground
--card
--card-foreground
--popover
--popover-foreground
```
## Text, Accent, Links, and Borders
```css
--text-primary
--text-secondary
--text-muted
--text-disabled
--text-placeholder
--text-on-accent
--accent
--accent-hover
--accent-active
--accent-subtle
--accent-border
--accent-text
--brand
--brand-hover
--brand-active
--brand-subtle
--brand-border
--brand-text
--link
--link-hover
--link-broken
--border
--border-subtle
--border-strong
--border-focus
--ring
```
## Typography
```css
--font-ui
--font-editor
--font-mono
--ui-font-size
--ui-font-weight
--ui-line-height
--editor-font-size
--editor-font-weight
--editor-line-height
--editor-paragraph-spacing
```
User appearance settings may override font families and font sizes. Themes should still define
weights and line heights.
## Components and Chrome
```css
--btn-primary-bg
--btn-primary-text
--btn-primary-hover
--input-bg
--input-border
--input-focus-ring
--menu-bg
--menu-border
--menu-shadow
--menu-hover
--modal-bg
--modal-border
--modal-shadow
--tooltip-bg
--tooltip-text
--sidebar-bg
--sidebar-border
--sidebar-tree-guide
--settings-group-bg
--settings-group-border
--settings-group-divider
--tab-bg
--tab-active-bg
--tab-accent
--statusbar-bg
--statusbar-border
--scrollbar-thumb
--scrollbar-hover
--shadow-raised
--shadow-floating
--shadow-overlay
--radius
```
## Status Colors
```css
--status-error
--error-bg
--status-error-foreground
--status-error-border
--status-error-on-solid
--status-success
--success-bg
--status-success-foreground
--status-success-border
--status-success-on-solid
--status-warning
--warning-bg
--status-warning-foreground
--status-warning-border
--status-warning-on-solid
--destructive
--destructive-foreground
```
## Editor, Syntax, and Markdown
```css
--syntax-keyword
--syntax-string
--syntax-comment
--syntax-number
--syntax-function
--syntax-type
--syntax-operator
--syntax-property
--syntax-heading
--syntax-meta
--editor-selection-bg
--editor-search-match-bg
--editor-search-match-active-bg
--markdown-content-width
--markdown-content-gutter
--markdown-block-radius
--markdown-block-spacing
--markdown-code-font-family
--markdown-code-font-size
--markdown-code-padding-inline
--markdown-code-padding-block
--markdown-callout-padding-block
--markdown-callout-padding-inline-start
--markdown-callout-padding-inline-end
```
## Headings and Tags
```css
--heading-font-weight
--normal-weight
--inline-title-margin-bottom
--h1-font-size
--h2-font-size
--h3-font-size
--h4-font-size
--h5-font-size
--h6-font-size
--h1-color
--h2-color
--h3-color
--h4-color
--h5-color
--h6-color
--tag-bg
--tag-active-bg
--tag-text
--tag-active-text
--tag-font-size
--tag-font-weight
--tag-border-radius
--tag-padding
```
---
# Stable Hooks and Selectors
Use documented body classes and data hooks when theme variables are not enough.
Source: https://cortex-md.tech/docs/themes/stable-hooks-and-selectors
Section: Theme Development
Prefer CSS variables first. When variables are not enough, use stable hooks that Cortex exposes for
themes and surfaces.
## Theme Root
Scope each variant to its runtime body class:
```css
body.theme-warm-notes-light {
color-scheme: light;
--bg-primary: #fbfaf6;
}
body.theme-warm-notes-dark {
color-scheme: dark;
--bg-primary: #201f1d;
}
```
Use `body[data-theme-scheme="dark"]` and `body[data-theme-scheme="light"]` when a rule should
follow the effective color scheme instead of a specific theme family.
## Markdown
Use `.markdown-surface` for rendered Markdown and editor-projected Markdown styling:
```css
body.theme-warm-notes-light .markdown-surface blockquote {
border-left-color: var(--accent);
}
```
Plugin `styles.css` files are also scoped to `.markdown-surface`, but community themes can style
the broader app.
## Overlays and Popups
Stable overlay hooks include:
- `[data-popup-surface]`
- `[data-command-surface]`
- `[data-slot="dialog-content"]`
- `[data-slot="alert-dialog-content"]`
- `[data-slot="dropdown-menu-content"]`
- `[data-slot="context-menu-content"]`
- `[data-slot="select-content"]`
- `[data-slot="popover-content"]`
- `[data-slot="hover-card-content"]`
Example:
```css
body.theme-warm-notes-light [data-popup-surface] {
box-shadow: 0 0 0 1px color-mix(in srgb, var(--border) 70%, transparent),
0 18px 48px color-mix(in srgb, black 12%, transparent);
}
```
## App Shell
Use shell selectors sparingly:
- `.app-shell`
- `.app-titlebar`
- `.app-content`
- `.app-sidebar`
- `.app-main`
- `.tab-bar`
- `.tab-trigger`
- `.statusbar-item`
- `.file-tree-item`
These are stronger than tokens and should be reserved for theme-specific polish that variables
cannot express.
## Avoid
- Private generated class names.
- Selectors that depend on deep child order.
- Global resets such as `* { ... }`.
- `!important` unless overriding explicit user appearance settings is truly intended.
- Built-in theme selectors such as `.theme-ink` for community theme behavior.
---
# Markdown and Callouts
Style Cortex Markdown surfaces, code blocks, headings, and callout colors through theme CSS.
Source: https://cortex-md.tech/docs/themes/markdown-and-callouts
Section: Theme Development
Markdown styling is shared by Live Preview, Reading View, and rendered plugin Markdown nodes. Theme
changes should keep those surfaces visually aligned.
## Layout Variables
```css
body.theme-warm-notes-light {
--markdown-content-width: 760px;
--markdown-content-gutter: 40px;
--markdown-block-radius: 8px;
--markdown-block-spacing: 1em;
}
```
Avoid adding vertical padding, margins, transforms, or non-baseline alignment to editable
CodeMirror line content. Prefer variables and non-disruptive visual chrome.
## Code
```css
body.theme-warm-notes-light {
--markdown-code-font-family: Menlo, Monaco, Consolas, "Liberation Mono", monospace;
--markdown-code-font-size: 14px;
--markdown-code-padding-inline: 16px;
--markdown-code-padding-block: 8px;
--bg-code: #f2efe8;
}
```
Markdown code spans and fenced code blocks use editor typography by default. Smaller typography
belongs on code block chrome, not necessarily the code itself.
## Headings
```css
body.theme-warm-notes-light {
--heading-font-weight: 650;
--h1-font-size: 1.45em;
--h2-font-size: 1.25em;
--h3-font-size: 1.08em;
--h1-color: var(--syntax-heading);
--h2-color: var(--syntax-heading);
}
```
Keep heading scale compact enough that editing remains calm and dense.
## Callouts
Standard callout variables use this pattern:
```css
body.theme-warm-notes-light {
--callout-note-color: var(--accent);
--callout-note-bg: var(--accent-subtle);
--callout-warning-color: var(--status-warning);
--callout-warning-bg: var(--warning-bg);
}
```
Common built-in callout names include `note`, `abstract`, `info`, `todo`, `tip`, `success`,
`question`, `warning`, `failure`, `danger`, `bug`, `example`, and `quote`.
Plugins can register or override callout types. Explicit plugin colors take precedence over theme
defaults for those registered callouts.
---
# Light, Dark, and System Modes
Pair community theme CSS files with Cortex appearance settings and effective color scheme.
Source: https://cortex-md.tech/docs/themes/light-dark-and-system
Section: Theme Development
Community themes provide paired light and dark CSS files. Cortex chooses the active variant from
appearance settings and system color-scheme when the user selects system mode.
## Runtime Classes
For manifest name `warm-notes`, Cortex applies:
```html
```
or:
```html
```
Write variant-specific variables inside the runtime class.
## Scheme Selectors
Use `data-theme-scheme` for rules that should follow the effective scheme:
```css
body[data-theme-scheme="dark"] .markdown-surface img {
filter: brightness(0.92);
}
```
Do not write community theme behavior against built-in selectors such as `.theme-ink` or
`.theme-paper`.
## Color Scheme
Set the browser color scheme in each file:
```css
body.theme-warm-notes-light {
color-scheme: light;
}
body.theme-warm-notes-dark {
color-scheme: dark;
}
```
This helps native form controls, scrollbars, and browser defaults align with the theme.
## Pairing Guidance
- Keep variable names aligned between `light.css` and `dark.css`.
- Test overlays, settings, editor selection, search matches, callouts, and code blocks in both
variants.
- Keep dark backgrounds neutral enough that syntax colors and accent states stay readable.
- Recompute foregrounds for contrast instead of reusing light-mode foregrounds in dark mode.
---
# Publishing and Review
Prepare theme release assets for review and community listing.
Source: https://cortex-md.tech/docs/themes/publishing-and-review
Section: Theme Development
Reviewed community listing installs theme assets into the active vault.
## Required Release Assets
Publish:
```text
manifest.json
light.css
dark.css
```
The CSS filenames can differ when the manifest references different safe relative paths.
For each colorscheme, Cortex looks for:
1. The exact relative path from `manifest.colorschemes`.
2. The basename of that path.
3. `.css`, such as `light.css` or `dark.css`.
## Manifest checks
The reviewed listing flow verifies:
- `manifest.json` is valid JSON.
- `manifest.id` matches the listing entry id.
- `colorschemes.light` and `colorschemes.dark` are safe relative paths.
- Both stylesheet assets exist before replacing an installed theme.
## Install and rollback behavior
Cortex installs into:
```text
/.cortex/themes/
```
Theme install and update use staging inside the vault themes directory. Cortex preserves the
previous installation until staging is promoted and community themes reload successfully. On
failure, it removes staging and restores the previous theme best effort.
## Release checklist
1. Include `manifest.json`.
2. Include both light and dark CSS assets.
3. Keep stylesheet paths relative and free of `..`.
4. Test applying the theme from light, dark, and system appearance modes.
5. Test uninstall while the theme is active.
---
# Theme Best Practices
Keep community themes native-feeling, readable, performant, and respectful of user overrides.
Source: https://cortex-md.tech/docs/themes/best-practices
Section: Theme Development
Good community themes feel native to Cortex while still having a clear voice.
## Use tokens first
Prefer semantic variables such as `--bg-primary`, `--text-primary`, `--accent`,
`--modal-bg`, and `--markdown-content-width` before writing component selectors.
Tokens survive UI refactors better than deep selectors.
## Keep contrast strong
- Body text should meet at least 4.5:1 contrast.
- Focus indicators should meet at least 3:1 contrast against adjacent surfaces.
- Test disabled, muted, warning, error, and selected states.
- Do not rely on color alone for important state.
## Respect user overrides
Users may override font family, UI font size, editor font size, and accent preferences. Avoid
heavy-handed `!important` rules that fight explicit user choices.
## Keep CSS cheap
- Avoid expensive global selectors.
- Avoid broad `backdrop-filter` rules.
- Avoid large data URLs and embedded images.
- Avoid animating layout properties.
- Prefer `opacity`, `transform`, and token changes for subtle motion.
## Scope Carefully
Good:
```css
body.theme-warm-notes-light [data-popup-surface] {
border-radius: 12px;
}
```
Risky:
```css
div > div > div:nth-child(2) {
border-radius: 12px;
}
```
## Test checklist
- Main editor in source, Live Preview, Reading View, and Side-by-Side.
- Command palette, quick switcher, dropdowns, popovers, context menus, and modals.
- Settings window/modal fallback.
- File explorer, tabs, status bar, bookmarks, tags, and properties.
- Markdown headings, tables, code blocks, links, callouts, task lists, and blockquotes.
- Light, dark, and system appearance modes.
---
# Cortex CLI
Use the CLI for local plugin and theme development against a real Cortex vault.
Source: https://cortex-md.tech/docs/developers/cli
Section: Developer Tools
The CLI mirrors the extension loop: create a project, run against a local vault, build, validate, and prepare artifacts when the work is ready.
## Create
Start with a small plugin or theme project. Keep the project close to the vault you use for testing so the feedback loop stays short.
```sh title="Create a plugin"
npm install -g @cortex.md/cli
cortex plugin create github-emoji
cd github-emoji
bun install
```
## Dev vault
Use a real local folder while developing. The goal is to test the same Markdown files, commands, and views that a user will touch.
```sh title="Run against a local vault"
cortex plugin dev --vault ~/notes
```
## Build and validate
Build production output and validate structure before publishing or sharing artifacts.
```sh title="Prepare release output"
cortex plugin build
cortex plugin validate
cortex plugin publish
```
## Related docs