laurent22/joplin · error · Error

No published share for folder: ${folderId}

Error message

No published share for folder: ${folderId}

What it means

Thrown by ShareService.unpublishFolder() when the in-memory share list (this.shares, backed by this.state.shares) contains no share with type ShareType.PublishedFolder whose folder_id matches. The shares list is a local cache populated from GET api/shares via refreshShares(); the method does not re-fetch before checking, so the guard fires both when the folder was never published and when the cache is out of date.

Source

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

		const folder = await Folder.load(folderId);
		if (!folder) throw new Error(`No such folder: ${folderId}`);

		const share = await this.api().exec('POST', 'api/shares', {}, {
			folder_id: folderId,
			type: ShareType.PublishedFolder,
		});

		await this.refreshShares();

		return share;
	}

	public async unpublishFolder(folderId: string): Promise<void> {
		const folder = await Folder.load(folderId);
		if (!folder) throw new Error(`No such folder: ${folderId}`);

		const share = this.shares.find(s => s.type === ShareType.PublishedFolder && s.folder_id === folderId);
		if (!share) throw new Error(`No published share for folder: ${folderId}`);

		const remainingShares = this.shares.filter(s => s.id !== share.id);
		const folderIds = [folderId, ...(await Folder.allChildrenFolders(folderId)).map(f => f.id)];
		const folders = await Folder.loadItemsByIds(folderIds) as FolderEntity[];
		const noteIds = (await Promise.all(folderIds.map(id => Folder.noteIds(id, { includeConflicts: true, includeDeleted: true })))).flat();
		const notes = await Note.loadItemsByIds(noteIds) as NoteEntity[];
		const directlyPublishedNoteIds = new Set(remainingShares
			.filter(s => s.type === ShareType.Note && !!s.note_id)
			.map(s => s.note_id));

		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));
		}

View on GitHub (pinned to dc4e0b464e)

Solutions

  1. Call await shareService.refreshShares() immediately before unpublishFolder so this.shares reflects the server.
  2. Treat 'no published share' as a no-op: check shares.some(s => s.type === ShareType.PublishedFolder && s.folder_id === folderId) first and skip if false.
  3. Confirm the folder was published via publishFolder() (PublishedFolder share), not merely shared as a notebook (Folder share) — the latter needs a different code path.
  4. Guard the UI/action against double invocation (disable the button while the request is in flight).

Example fix

// before
await shareService.unpublishFolder(folderId);

// after
await shareService.refreshShares();
const isPublished = shareService.shares.some(
	s => s.type === ShareType.PublishedFolder && s.folder_id === folderId,
);
if (isPublished) await shareService.unpublishFolder(folderId);
Defensive patterns

Strategy: validation

Validate before calling

import { ShareType } from '@joplin/lib/services/share/ShareService';

await shareService.refreshShares();
const isPublished = shareService.shares.some(
	s => s.type === ShareType.PublishedFolder && s.folder_id === folderId,
);
if (!isPublished) return; // idempotent no-op instead of a thrown error

Type guard

const hasPublishedShare = (shares: StateShare[], folderId: string): boolean =>
	shares.some(s => s.type === ShareType.PublishedFolder && s.folder_id === folderId);

Try / catch

try {
	await shareService.unpublishFolder(folderId);
} catch (error) {
	if (error instanceof Error && error.message.startsWith('No published share for folder:')) {
		await shareService.refreshShares(); // state may be stale; retry once after refresh
		await shareService.unpublishFolder(folderId);
	} else {
		throw error;
	}
}

Prevention

When it happens

Trigger: Calling unpublishFolder on a folder that was shared as a normal notebook (ShareType.Folder) instead of published (ShareType.PublishedFolder); calling unpublish twice in a row (second call finds no share); calling it right after app startup or after the share was created/deleted on another device, before refreshShares() has re-synced this.shares.

Common situations: Double-clicking an 'unpublish' button or re-running a batch script so the operation executes twice; mixing up the share types (Folder vs PublishedFolder vs Note) when building automation; shares state stale after a publish/unpublish from the web clipper or another client on the same account.

Related errors


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