Skip to main content
The Editor API provides a unified interface for working with the Obsidian editor, whether in Source mode or Live Preview. This guide covers the essential operations for reading and manipulating editor content.

Accessing the Editor

Get Active Editor

Access the current editor through the workspace:
import { Editor, MarkdownView, Plugin } from 'obsidian';

export default class EditorPlugin extends Plugin {
  async onload() {
    this.addCommand({
      id: 'example-editor-command',
      name: 'Example editor command',
      editorCallback: (editor: Editor, view: MarkdownView) => {
        // Work with editor here
        console.log(editor.getValue());
      }
    });
  }
}

Get Editor from Active Leaf

const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (view) {
  const editor = view.editor;
  // Use editor
}
The editorCallback in commands automatically provides the editor when a markdown view is active.

Reading Content

Get All Content

// Get entire document
const content = editor.getValue();

// Get line count
const lineCount = editor.lineCount();

// Get last line number
const lastLine = editor.lastLine();

Get Line Content

// Get specific line (0-indexed)
const line = editor.getLine(5);

// Get line at cursor
const cursor = editor.getCursor();
const currentLine = editor.getLine(cursor.line);

Get Range Content

import { EditorPosition } from 'obsidian';

const from: EditorPosition = { line: 0, ch: 0 };
const to: EditorPosition = { line: 5, ch: 10 };

const rangeContent = editor.getRange(from, to);

Get Selection

// Get selected text
const selection = editor.getSelection();

// Check if anything is selected
if (editor.somethingSelected()) {
  console.log('Selection:', editor.getSelection());
}

// Get all selections (for multi-cursor)
const selections = editor.listSelections();
selections.forEach(sel => {
  console.log('From:', sel.anchor, 'To:', sel.head);
});

Writing Content

Replace Entire Content

editor.setValue('New content for the entire document');
Replacing all content will clear the undo history. Use carefully.

Replace Line

// Replace line 5
editor.setLine(5, 'New line content');

Replace Range

const from: EditorPosition = { line: 2, ch: 0 };
const to: EditorPosition = { line: 2, ch: 10 };

editor.replaceRange('replacement text', from, to);

Replace Selection

// Replace current selection
editor.replaceSelection('New text');

// Insert at cursor if nothing selected
if (!editor.somethingSelected()) {
  editor.replaceSelection('Inserted text');
}

Cursor and Selection Management

Get Cursor Position

// Get cursor position
const cursor = editor.getCursor();
console.log(`Line: ${cursor.line}, Column: ${cursor.ch}`);

// Get cursor from selection start
const from = editor.getCursor('from');
const to = editor.getCursor('to');

Set Cursor Position

// Set cursor to position
editor.setCursor({ line: 10, ch: 5 });

// Alternative syntax
editor.setCursor(10, 5);

Set Selection

const anchor: EditorPosition = { line: 5, ch: 0 };
const head: EditorPosition = { line: 5, ch: 20 };

// Select range
editor.setSelection(anchor, head);

// Select entire line
const lineLength = editor.getLine(5).length;
editor.setSelection(
  { line: 5, ch: 0 },
  { line: 5, ch: lineLength }
);

Multiple Selections

import { EditorSelectionOrCaret } from 'obsidian';

const selections: EditorSelectionOrCaret[] = [
  { anchor: { line: 1, ch: 0 }, head: { line: 1, ch: 5 } },
  { anchor: { line: 3, ch: 0 }, head: { line: 3, ch: 5 } },
  { anchor: { line: 5, ch: 0 }, head: { line: 5, ch: 5 } }
];

editor.setSelections(selections);

Position and Offset Conversion

Convert Position to Offset

const pos: EditorPosition = { line: 5, ch: 10 };
const offset = editor.posToOffset(pos);

console.log(`Position ${pos.line}:${pos.ch} is at offset ${offset}`);

Convert Offset to Position

const offset = 150;
const pos = editor.offsetToPos(offset);

console.log(`Offset ${offset} is at line ${pos.line}, column ${pos.ch}`);
Offsets are useful when working with regex matches or processing the entire document as a single string.

Editor Commands

Execute Built-in Commands

import { EditorCommandName } from 'obsidian';

// Available commands
editor.exec('goUp');
editor.exec('goDown');
editor.exec('goLeft');
editor.exec('goRight');
editor.exec('goStart');
editor.exec('goEnd');
editor.exec('goWordLeft');
editor.exec('goWordRight');
editor.exec('indentMore');
editor.exec('indentLess');
editor.exec('newlineAndIndent');
editor.exec('swapLineUp');
editor.exec('swapLineDown');
editor.exec('deleteLine');
editor.exec('toggleFold');
editor.exec('foldAll');
editor.exec('unfoldAll');

Editor Transactions

Perform multiple changes atomically:
import { EditorTransaction } from 'obsidian';

const transaction: EditorTransaction = {
  changes: [
    {
      from: { line: 0, ch: 0 },
      to: { line: 0, ch: 5 },
      text: 'Hello'
    },
    {
      from: { line: 2, ch: 0 },
      to: { line: 2, ch: 10 },
      text: 'World'
    }
  ]
};

editor.transaction(transaction);

Transaction with Selection

const transaction: EditorTransaction = {
  changes: [
    {
      from: { line: 0, ch: 0 },
      text: '# '
    }
  ],
  selection: {
    from: { line: 0, ch: 2 }
  }
};

editor.transaction(transaction);

Working with Words

Get Word at Position

const cursor = editor.getCursor();
const wordRange = editor.wordAt(cursor);

if (wordRange) {
  const word = editor.getRange(wordRange.from, wordRange.to);
  console.log('Word at cursor:', word);
}

Scrolling

Get Scroll Info

const scrollInfo = editor.getScrollInfo();
console.log('Top:', scrollInfo.top);
console.log('Left:', scrollInfo.left);

Set Scroll Position

// Scroll to position
editor.scrollTo(0, 100);

// Scroll horizontally only
editor.scrollTo(50, null);

// Scroll vertically only
editor.scrollTo(null, 200);

Scroll Range into View

const range = {
  from: { line: 10, ch: 0 },
  to: { line: 10, ch: 20 }
};

// Scroll range into view
editor.scrollIntoView(range);

// Center the range
editor.scrollIntoView(range, true);

Process Lines

Process multiple lines efficiently:
editor.processLines(
  // Read function
  (lineNum, lineText) => {
    // Return value to pass to write function
    if (lineText.startsWith('TODO:')) {
      return lineText.replace('TODO:', 'DONE:');
    }
    return null; // No changes
  },
  // Write function
  (lineNum, lineText, value) => {
    if (value) {
      return {
        from: { line: lineNum, ch: 0 },
        to: { line: lineNum, ch: lineText.length },
        text: value
      };
    }
  }
);

Focus Management

// Focus editor
editor.focus();

// Remove focus
editor.blur();

// Check if focused
if (editor.hasFocus()) {
  console.log('Editor is focused');
}

Undo/Redo

// Undo last change
editor.undo();

// Redo
editor.redo();

Refresh Editor

// Force editor refresh
editor.refresh();

Practical Examples

Insert Text at Cursor

function insertAtCursor(editor: Editor, text: string) {
  const cursor = editor.getCursor();
  editor.replaceRange(text, cursor);
  editor.setCursor({
    line: cursor.line,
    ch: cursor.ch + text.length
  });
}

insertAtCursor(editor, '**bold text**');

Wrap Selection

function wrapSelection(editor: Editor, before: string, after: string) {
  const selection = editor.getSelection();
  const wrapped = before + selection + after;
  editor.replaceSelection(wrapped);
}

wrapSelection(editor, '**', '**'); // Bold
wrapSelection(editor, '[[', ']]');  // Link

Toggle Line Prefix

function toggleLinePrefix(editor: Editor, prefix: string) {
  const cursor = editor.getCursor();
  const line = editor.getLine(cursor.line);

  if (line.startsWith(prefix)) {
    editor.setLine(cursor.line, line.slice(prefix.length));
  } else {
    editor.setLine(cursor.line, prefix + line);
  }
}

toggleLinePrefix(editor, '- [ ] '); // Toggle checkbox

Find and Replace

function findAndReplace(editor: Editor, find: string, replace: string) {
  const content = editor.getValue();
  const newContent = content.replaceAll(find, replace);
  editor.setValue(newContent);
}

findAndReplace(editor, 'old text', 'new text');

Best Practices

1
Use Editor Callbacks
2
Prefer editorCallback in commands for automatic editor access:
3
this.addCommand({
  id: 'my-command',
  name: 'My Command',
  editorCallback: (editor, view) => {
    // Editor is already available
  }
});
4
Batch Changes with Transactions
5
Use transactions for multiple changes to maintain undo history:
6
// Good: Single transaction
editor.transaction({
  changes: [change1, change2, change3]
});

// Avoid: Multiple separate operations
editor.replaceRange(...);
editor.replaceRange(...);
editor.replaceRange(...);
7
Preserve Cursor Position
8
Restore cursor after modifications:
9
const cursor = editor.getCursor();
// Make changes
editor.setCursor(cursor);

Working with Files

Read and write files in the vault

Settings and Commands

Create plugin commands

Build docs developers (and LLMs) love