laurent22/joplin · error · Error

Locked notes cannot be opened in an external editor

Error message

Locked notes cannot be opened in an external editor

What it means

Thrown by the startExternalEditing command when EEE (Encryption Master Password / note lock) is enabled and the target note has is_locked true. Locked notes are encrypted at rest; opening them in an external editor would either expose ciphertext to the editor process or let plaintext be written to an unencrypted temp file, so the command refuses.

Source

Thrown at packages/app-desktop/commands/startExternalEditing.ts:24

import isNoteLockEnabled from '@joplin/lib/services/noteLock/isNoteLockEnabled';
import bridge from '../services/bridge';

export const declaration: CommandDeclaration = {
	name: 'startExternalEditing',
	label: () => _('Open in external editor'),
	iconName: 'icon-share',
};

export const runtime = (): CommandRuntime => {
	return {
		execute: async (context: CommandContext, noteId: string = null) => {
			noteId = noteId || stateUtils.selectedNoteId(context.state);

			try {
				const note = await Note.load(noteId);
				// The enabled condition can be bypassed when the command is executed directly (e.g. via
				// toggleExternalEditing): the external editor would get ciphertext, or write plaintext to disk.
				if (isNoteLockEnabled() && note.is_locked) throw new Error(_('Locked notes cannot be opened in an external editor'));
				void ExternalEditWatcher.instance().openAndWatch(note);
			} catch (error) {
				bridge().showErrorMessageBox(_('Error opening note in editor: %s', error.message));
			}
		},
		enabledCondition: 'oneNoteSelected && !noteIsReadOnly && !noteIsLocked',
	};
};

View on GitHub (pinned to 2654b33620)

Solutions

  1. Unlock the note (remove is_locked) before launching the external editor.
  2. Disable note locking (clear the master password / lock feature) if external editing is required for that note.
  3. Route calls through the command service so enabledCondition (`!noteIsLocked`) disables the action rather than throwing.
  4. In a plugin, check note.is_locked and isNoteLockEnabled() before calling execute().

Example fix

// before
if (isNoteLockEnabled() && note.is_locked) throw new Error(_('Locked notes cannot be opened in an external editor'));

// after — guard before invoking, surface a user-actionable hint
if (isNoteLockEnabled() && note.is_locked) {
  throw new Error(_('Locked notes cannot be opened in an external editor. Unlock the note first.'));
}
Defensive patterns

Strategy: validation

Validate before calling

const note = await Note.load(noteId);
if (isNoteLockEnabled() && note?.is_locked) {
  // ask user to unlock, or abort — do not call startExternalEditing
  return;
}
await CommandService.instance().execute('startExternalEditing', noteId);

Type guard

function canOpenInExternalEditor(note: NoteEntity | null): boolean {
  return !!note && !(isNoteLockEnabled() && note.is_locked);
}

Try / catch

try {
  await CommandService.instance().execute('startExternalEditing', noteId);
} catch (e) {
  if (e.message.includes('Locked notes')) {
    // prompt the user to unlock the note first
  } else throw e;
}

Prevention

When it happens

Trigger: Executing startExternalEditing (directly or via toggleExternalEditing) on a note where isNoteLockEnabled() returns true AND note.is_locked is true. The enabledCondition already excludes locked notes, but a direct execute() call bypasses enabled checks, hence the in-body guard.

Common situations: A toolbar/keyboard shortcut wired directly to execute() instead of going through the enabled-condition gate; programmatic command invocation from a plugin; the note was locked after the menu was rendered so the enabled state is stale.

Related errors


AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12). Data as JSON: /api/errors/900c8472f51fdd96. Report an issue: GitHub.