Skip to main content
The Fresh editor provides a powerful plugin API that enables you to extend the editor’s functionality using TypeScript. Plugins can create custom commands, respond to events, manipulate buffers, and create rich UI elements.

Core Concepts

Buffers

A buffer holds text content and may or may not be associated with a file. Each buffer has a unique numeric ID that persists for the editor session. Buffers track their content, modification state, cursor positions, and path. All text operations (insert, delete, read) use byte offsets, not character indices.

Splits

A split is a viewport pane that displays a buffer. The editor can have multiple splits arranged in a tree layout. Each split shows exactly one buffer, but the same buffer can be displayed in multiple splits. Use split IDs to control which pane displays which buffer.

Virtual Buffers

Special buffers created by plugins to display structured data like search results, diagnostics, or git logs. Virtual buffers support text properties (metadata attached to text ranges) that plugins can query when the user selects a line. Unlike normal buffers, virtual buffers are typically read-only and not backed by files.

Text Properties

Metadata attached to text ranges in virtual buffers. Each entry has text content and a properties object with arbitrary key-value pairs. Use getTextPropertiesAtCursor to retrieve properties at the cursor position (e.g., to get file/line info for “go to”).

Overlays

Visual decorations applied to buffer text without modifying content. Overlays can change text color and add underlines. Use overlay IDs to manage them; prefix IDs enable batch removal (e.g., “lint:” prefix for all linter highlights).

Modes

Keybinding contexts that determine how keypresses are interpreted. Each buffer has a mode (e.g., “normal”, “insert”, “special”). Custom modes can inherit from parents and define buffer-local keybindings. Virtual buffers typically use custom modes.

Quick Start

Creating Your First Plugin

Plugins are TypeScript files that interact with the editor through the global editor object:
// my-plugin.ts

// Register a command
editor.registerCommand(
  "My Plugin: Hello",
  "Say hello from my plugin",
  "my_hello",
  "normal",
  "plugin"
);

// Define the command handler
globalThis.my_hello = () => {
  editor.setStatus("Hello from my plugin!");
};

Responding to Events

Plugins can subscribe to editor events using the on method:
globalThis.onBufferSave = (data) => {
  editor.setStatus(`Saved: ${data.path}`);
};

editor.on("buffer_save", "onBufferSave");

Creating a Virtual Buffer

Virtual buffers are useful for displaying structured data:
// First define the mode with keybindings
editor.defineMode("search-results", "special", [
  ["Return", "search_goto"],
  ["q", "close_buffer"]
], true);

// Then create the buffer
const result = await editor.createVirtualBufferInSplit({
  name: "*Search*",
  mode: "search-results",
  read_only: true,
  entries: [
    { text: "src/main.rs:42: match\n", properties: { file: "src/main.rs", line: 42 } }
  ],
  ratio: 0.3,
  panel_id: "search"
});

API Categories

The Fresh Plugin API is organized into several categories:

Buffer API

Query and manipulate buffer content, cursors, and metadata

Events API

Subscribe to editor events and hooks

Filesystem API

Read, write, and manipulate files and directories

Overlays API

Add visual decorations to buffer text

Virtual Buffers

Create and manage virtual buffers for structured data

Status & Logging

Display status messages and log plugin activity

Internationalization

Add multi-language support to your plugin

Utilities

Reusable utilities for common plugin patterns

Common Types

The API uses several TypeScript types that are shared across methods:

BufferInfo

interface BufferInfo {
  id: number;           // Unique buffer ID
  path: string;         // File path (empty string if no path)
  modified: boolean;    // Whether buffer has unsaved changes
  length: number;       // Buffer length in bytes
}

CursorInfo

interface CursorInfo {
  position: number;                           // Byte position of the cursor
  selection: { start: number; end: number } | null;  // Selection range if text is selected
}

TextPropertyEntry

interface TextPropertyEntry {
  text: string;                    // Text to display
  properties: Record<string, unknown>;  // Arbitrary metadata
}

SpawnResult

interface SpawnResult {
  stdout: string;      // Complete stdout as string
  stderr: string;      // Complete stderr as string
  exit_code: number;   // Process exit code (0 = success)
}

Best Practices

Reference types/fresh.d.ts for autocomplete and type checking in your plugin development.
Use "myplugin:something" format for easy batch removal of overlays.
Wrap async operations in try/catch blocks to provide graceful error handling.
Use batched events like lines_changed instead of per-keystroke handlers to avoid performance issues.
Use editor.debug() to log values during development and troubleshooting.
Add .i18n.json files to make your plugin accessible to international users.

Next Steps

1

Explore the Buffer API

Learn how to query and manipulate buffer content in the Buffer API reference.
2

Subscribe to Events

Discover available events in the Events API documentation.
3

Create Virtual Buffers

Build rich UI elements with Virtual Buffers.
4

Add Internationalization

Make your plugin multilingual with i18n support.

Build docs developers (and LLMs) love