laurent22/joplin · error · Error

No such note: ${noteId}

Error message

No such note: ${noteId}

What it means

Thrown by ShareService.unshareNote() when Note.load(noteId) returns null, i.e. the note does not exist in the local database. As with the folder guards, share services operate on the local SQLite/IndexedDB content, so the note must already be synced to this device. The check runs before loadSharesByItem() hits the network, so no API call is made when it fires.

Source

Thrown at packages/lib/services/share/ShareService.ts:358

		for (const folderItem of folders) {
			await Folder.updateShareStatus({ ...folderItem, type_: ModelType.Folder }, false);
		}

		for (const note of notes) {
			await Note.updateShareStatus({ ...note, type_: ModelType.Note }, directlyPublishedNoteIds.has(note.id));
		}

		await Folder.updateAllShareIds(ResourceService.instance(), remainingShares);

		// Clean local state first so next sync can recover if deletion stops midway
		await this.deleteShare(share.id);
		await this.refreshShares();
	}

	public async unshareNote(noteId: string) {
		const note = await Note.load(noteId);
		if (!note) throw new Error(`No such note: ${noteId}`);

		const noteShares = (await this.loadSharesByItem(noteId))
			.filter(s => s.type === ShareType.Note);

		const promises: Promise<void>[] = [];

		for (const share of noteShares) {
			promises.push(this.deleteShare(share.id));
		}

		await Promise.all(promises);

		await Note.save({
			id: note.id,
			parent_id: note.parent_id,
			is_shared: 0,
			updated_time: Date.now(),
		}, {

View on GitHub (pinned to dc4e0b464e)

Solutions

  1. Validate first: const note = await Note.load(noteId); skip or surface a friendly message if null.
  2. Re-run sync before unsharing so notes created/deleted elsewhere are reflected locally.
  3. Audit where the noteId comes from — if it originates from share.note_id, the share may outlive the note; handle that case explicitly.
  4. If the goal is a clean slate, delete the orphan share directly via deleteShare(share.id) instead of unshareNote().

Example fix

// before
await shareService.unshareNote(noteId);

// after
const note = await Note.load(noteId);
if (!note) return; // note gone locally; nothing to unshare
await shareService.unshareNote(noteId);
Defensive patterns

Strategy: validation

Validate before calling

import { Note } from '@joplin/lib/models/Note';

const note = await Note.load(noteId);
if (!note) {
	return; // note absent locally; nothing to unshare
}

Type guard

const isExistingNote = (n: NoteEntity | null): n is NoteEntity => !!n;

Try / catch

try {
	await shareService.unshareNote(noteId);
} catch (error) {
	if (error instanceof Error && error.message.startsWith('No such note:')) {
		// Note deleted locally; optionally delete orphan shares via deleteShare(share.id)
	} else {
		throw error;
	}
}

Prevention

When it happens

Trigger: Calling unshareNote() with a non-existent or malformed note ID; the note was deleted locally (or moved to trash and purged) before the call; the note exists only on another device/profile and was never synced here; a note ID from a share object (share.note_id) whose underlying note was removed.

Common situations: Plugins or scripts that read share.note_id from a stale share list after the source note was deleted; race between a delete action and an unshare action in the UI; test fixtures that build share records without the matching note rows.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of laurent22/joplin@dc4e0b464e (2026-08-21). Data as JSON: /api/errors/8baeb1543b5d5d0b. Report an issue: GitHub.