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

  1. Log the full response payload when the guard fires to identify what the server actually returned (HTML error page, error JSON, empty body).
  2. 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).
  3. Upgrade (or align) the Joplin Server version so GET api/shares supports the item param and returns { items: [...] }.
  4. 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

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


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