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
- Call await shareService.refreshShares() immediately before unpublishFolder so this.shares reflects the server.
- 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.
- Confirm the folder was published via publishFolder() (PublishedFolder share), not merely shared as a notebook (Folder share) — the latter needs a different code path.
- 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
- Call refreshShares() right before checking/using this.shares — the in-memory list is only as fresh as the last refresh.
- Make unpublish idempotent in the UI: disable the control while the request runs to prevent double invocation.
- Distinguish share types up front (PublishedFolder vs Folder vs Note) so the right unshare path is used.
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
- No such folder: ${folderId}
- No such note: ${noteId}
- Note not published: %s
- Could not verify the share status of this notebook - abortin
- Invalid response: Shares list is not an array. Was ${typeof
AI-assisted analysis of laurent22/joplin@dc4e0b464e (2026-08-21).
Data as JSON: /api/errors/87299132202edb7f.
Report an issue: GitHub.