laurent22/joplin · error · Error
Invalid response: Shares list is not an array. Was ${typeof
Error message
Invalid response: Shares list is not an array. Was ${typeof shares?.items}. What it means
Thrown by ShareService.loadSharesByItem() when the response to GET api/shares?item=<id> does not contain an array under .items. The client expects the Joplin Server paginated shape { items: StateShare[] }; if items is undefined, an object, or a string (typeof is interpolated into the message), the response came from an incompatible backend, a proxy, or an error page rather than a real shares listing. Note the code already tolerates older servers that ignore the item query param by filtering client-side afterwards.
Source
Thrown at packages/lib/services/share/ShareService.ts:474
))) {
return true;
}
// In some cases, an item can have is_shared = 1, but no share in `shares`. In this case,
// either the item has been unpublished remotely and not yet synced, or the item was published
// by another user. Send a network request to determine whether the item is actually published:
if (item.is_shared) {
const shares = (await this.loadSharesByItem(item.id))
.filter(isPublishedItemShare);
return shares.length > 0;
}
return false;
}
private async loadSharesByItem(itemId: string) {
const shares = await this.api().exec('GET', 'api/shares', { item: itemId });
if (!Array.isArray(shares?.items)) throw new Error(`Invalid response: Shares list is not an array. Was ${typeof shares?.items}.`);
const items: StateShare[] = shares.items.filter(
// For compatibility with older server versions that don't support search
(i: StateShare) => (
(i.type === ShareType.Note && i.note_id === itemId)
|| (i.type === ShareType.PublishedFolder && i.folder_id === itemId)
|| (i.type === ShareType.Folder && i.folder_id === itemId)
),
);
return items;
}
private async loadShares() {
return this.api().exec('GET', 'api/shares');
}
private async loadShareUsers(shareId: string) {
return this.api().exec('GET', `api/shares/${shareId}/users`);View on GitHub (pinned to dc4e0b464e)
Solutions
- Log the full response payload when the guard fires to identify what the server actually returned (HTML error page, error JSON, empty body).
- Verify the API base URL targets a compatible Joplin Server instance and that the account has access (a 4xx body often reaches this check when errors are swallowed upstream).
- Upgrade (or align) the Joplin Server version so GET api/shares supports the item param and returns { items: [...] }.
- If wrapping the API yourself, unwrap resp.data before returning so callers see the server envelope, not the transport object.
Example fix
// before
const shares = await this.api().exec('GET', 'api/shares', { item: itemId });
return shares.items.filter(matchItem);
// after
const resp = await this.api().exec('GET', 'api/shares', { item: itemId });
if (!Array.isArray(resp?.items)) {
throw new Error(`Invalid response: Shares list is not an array. Was ${typeof resp?.items}. Payload: ${JSON.stringify(resp)?.slice(0, 200)}`);
}
return resp.items.filter(matchItem); Defensive patterns
Strategy: type-guard
Type guard
interface SharesListResponse { items: StateShare[] }
const isSharesListResponse = (r: unknown): r is SharesListResponse =>
!!r && typeof r === 'object' && Array.isArray((r as SharesListResponse).items); Try / catch
try {
await shareService.isPublished(note, knownShares);
} catch (error) {
if (error instanceof Error && error.message.startsWith('Invalid response: Shares list is not an array')) {
// Backend/proxy mismatch: log the payload, verify server version and API base URL
} else {
throw error;
}
} Prevention
- Pin clients to a Joplin Server version compatible with the shares API (GET api/shares returning { items: [...] }).
- Ensure reverse proxies pass API errors through as real HTTP status codes instead of HTML error pages.
- When wrapping the API, return the parsed body (resp.data), never the Axios response object.
- Log the raw payload alongside the error to classify server-vs-proxy-vs-wrapper causes quickly.
When it happens
Trigger: Pointing api() at a Joplin Server older than the version that supports the item search param (or a non-Joplin backend) so the payload shape differs; an auth failure or rate-limit page returned as HTML/JSON without an items field; a reverse proxy or gateway rewriting the response; a server bug or partial deployment returning an error object where the client expects the list envelope.
Common situations: Self-hosted Joplin Server behind nginx/Cloudflare that intercepts errors and returns a branded error body; server upgraded or downgraded out of step with the client; custom API wrappers that return the Axios response object (so .data.items exists but .items does not); development against a mock server that omits the items wrapper.
Related errors
- No such folder: ${folderId}
- No published share for folder: ${folderId}
- No such note: ${noteId}
- mustUpgradeApp
AI-assisted analysis of laurent22/joplin@dc4e0b464e (2026-08-21).
Data as JSON: /api/errors/1a8cc09e1d4c0852.
Report an issue: GitHub.