laurent22/joplin · error · Error

Cannot convert read-only item: "%s"

Error message

Cannot convert read-only item: "%s"

What it means

Thrown by the convertNoteToMarkdown command when itemIsReadOnly() returns true for a note about to be converted. Read-only status is derived from Joplin Server sharing/ownership and the sync userId — a note owned by another user (shared notebook) cannot be converted, because conversion duplicates then trashes the original, which would destroy another user's content.

Source

Thrown at packages/lib/commands/convertNoteToMarkdown.ts:42

			if (typeof noteIds === 'string') {
				noteIds = [noteIds];
			}
			if (noteIds.length === 0) {
				noteIds = context.state.selectedNoteIds;
			}

			const notes: NoteEntity[] = await Note.loadItemsByIdsOrFail(noteIds);

			try {
				let isFirst = true;
				let processedCount = 0;
				for (const note of notes) {
					if (note.markup_language === MarkupLanguage.Markdown) {
						logger.warn('Skipping item: Already Markdown.');
						continue;
					}
					if (await itemIsReadOnly(Note, ModelType.Note, ItemChange.SOURCE_UNSPECIFIED, note.id, Setting.value('sync.userId'), context.state.shareService)) {
						throw new Error(_('Cannot convert read-only item: "%s"', note.title));
					}

					const markdownBody = await convertHtmlToMarkdown().execute(context, note.body);
					const newNote = await Note.duplicate(note.id);

					newNote.body = markdownBody;
					newNote.markup_language = MarkupLanguage.Markdown;
					newNote.user_created_time = note.user_created_time;
					newNote.user_updated_time = note.user_updated_time;
					newNote.updated_time = Date.now();

					await Note.save(newNote, { autoTimestamp: false });
					await Note.delete(note.id, { toTrash: true });
					processedCount ++;

					if (isFirst) {
						context.dispatch({
							type: 'NOTE_SELECT',

View on GitHub (pinned to 2654b33620)

Solutions

  1. Ask the notebook owner to convert the note, or to give you write ownership.
  2. Copy the note body out manually into a new Markdown note you own instead of using the convert command.
  3. If you believe you should own the note, verify the share/ownership settings on the Joplin Server.
  4. Avoid running the command on a multi-selection that includes shared notes.
Defensive patterns

Strategy: validation

Validate before calling

import { itemIsReadOnly } from './models/utils/readOnly';
if (await itemIsReadOnly(Note, ModelType.Note, ItemChange.SOURCE_UNSPECIFIED, note.id, Setting.value('sync.userId'), shareService)) {
  // do not attempt conversion; the command will throw
  showReadOnlyWarning(note);
  return;
}

Try / catch

try {
  await commandRuntime.execute({ ... } as CommandContext, noteIds);
} catch (error) {
  if (/Cannot convert read-only item/.test(error.message)) {
    // inform the user the note is shared/read-only
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: The user selects an HTML note that belongs to a shared folder they do not own (sync.userId !== note owner) and invokes 'Convert to Markdown'. itemIsReadOnly() consults the share service and returns true, so the conversion aborts before Note.duplicate runs.

Common situations: Working in a shared Joplin Server notebook where notes are owned by a colleague; the share service reports the note as read-only for the current user; the command's enabledCondition ('!noteIsReadOnly') was bypassed (e.g. multi-select where at least one note is read-only).

Related errors


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