laurent22/joplin · error · Error
Cannot convert read-only item
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.
Solutions
- Ask the notebook owner to convert the note, or to give you write ownership.
- Copy the note body out manually into a new Markdown note you own instead of using the convert command.
- If you believe you should own the note, verify the share/ownership settings on the Joplin Server.
- 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
- Check itemIsReadOnly() before offering the 'Convert to Markdown' action in the UI.
- Avoid multi-select conversions that include shared notes you don't own.
- Respect the command's enabledCondition ('!noteIsReadOnly') in custom integrations.
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
- Could not verify the share status of this notebook -…
- Cannot change the note lock of a read-only note
- isReadOnly
- Could not create directory
- could not create file at
AI-assisted analysis of laurent22/joplin@683240968b (2026-08-28).
Data as JSON: /api/errors/4e7655fe39727195.
Report an issue: GitHub.
Appendix: source
Thrown at packages/lib/commands/convertNoteToMarkdown.ts:51
const notes: NoteEntity[] = await Note.loadItemsByIdsOrFail(noteIds);
try {
const firstLockedNote = isNoteLockEnabled() ? notes.find(note => note.markup_language !== MarkupLanguage.Markdown && NoteLockNote.isLocked(note)) : null;
if (firstLockedNote && !NoteLockSession.instance().isUnlocked()) {
throw new Error(_('Cannot convert locked note: "%s"', firstLockedNote.title));
}
// Captured once so a session lock mid-run cannot fail the remaining conversions.
const noteLockKey = firstLockedNote ? NoteLockSession.instance().decryptedKey() : null;
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 noteIsLocked = isNoteLockEnabled() && NoteLockNote.isLocked(note);
// A locked note converts through a full gated load and save, so the body is
// decrypted for the conversion and the converted copy is encrypted again.
const sourceNote = noteIsLocked ? await Note.load(note.id, { useNoteLock: true, noteLockKey }) : note;
const markdownBody = await convertHtmlToMarkdown().execute(context, sourceNote.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();
const toSave = noteIsLocked ? { ...newNote, isDecrypted: true } : newNote;
await Note.save(toSave, { autoTimestamp: false, useNoteLock: noteIsLocked, noteLockKey });View on GitHub (pinned to 683240968b)