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
- 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 - abortin
- isReadOnly
- Note not published: %s
- Could not read directory: ${path}: ${error.message}
- Missing read-write access. It might be necessary to share th
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/4e7655fe39727195.
Report an issue: GitHub.