toeverything/AFFiNE · warning · BadRequest
Invalid pagination cursor
Error message
Invalid pagination cursor
What it means
Thrown by parseJsonCursor (workspace-analytics.ts:204) when the `after` cursor string is neither parseable by JSON.parse nor by the project's decodeWithJson helper. All analytics list endpoints that paginate by cursor pass through this function. UserFriendlyError BadRequest, code bad_request, HTTP 400.
Source
Thrown at packages/backend/server/src/models/workspace-analytics.ts:204
}
function parseJsonCursor<T>(cursor?: string | null): T | null {
if (!cursor) {
return null;
}
const raw = cursor.trim();
if (!raw) {
return null;
}
try {
return JSON.parse(raw) as T;
} catch {
try {
return decodeWithJson<T>(raw);
} catch {
throw new BadRequest('Invalid pagination cursor');
}
}
}
function parseCursorDate(value: unknown): Date {
if (
typeof value !== 'string' &&
typeof value !== 'number' &&
!(value instanceof Date)
) {
throw new BadRequest('Invalid pagination cursor');
}
const parsed = new Date(value);
if (Number.isNaN(parsed.getTime())) {
throw new BadRequest('Invalid pagination cursor');
}
return parsed;View on GitHub (pinned to 26c515e050)
Solutions
- Drop the cursor and request the first page (omit the `after` parameter).
- Re-encode the cursor using the server's encode helper.
- Treat cursors as opaque tokens and never construct or modify them client-side.
Example fix
// before GET /analytics?after=%%invalid // after GET /analytics // omit `after` to start from page 1
Defensive patterns
Strategy: validation
Validate before calling
function safeCursor(raw: string | null | undefined): string | null {
if (!raw) return null;
const s = raw.trim();
if (!s) return null;
try { JSON.parse(s); return s; } catch {}
try { decodeWithJson(s); return s; } catch {}
return null; // drop invalid cursor -> start from page 1
} Try / catch
import { BadRequest } from '../base/error/errors.gen';
try {
const cursor = parseJsonCursor(pagination.after);
} catch (e) {
if (e instanceof BadRequest) {
// drop the cursor and request the first page
} else throw e;
} Prevention
- Treat cursors as opaque; never edit or construct them client-side.
- On any cursor error, restart from page 1 rather than retrying the same value.
- Invalidate cached cursors after a server version upgrade.
When it happens
Trigger: Sending a malformed, truncated, or hand-edited `after` query parameter to any paginated analytics endpoint.
Common situations: Frontend cached an old cursor format across a server version upgrade; a proxy truncated the query string; a bookmarked URL contains a cursor that is no longer valid; client treated the cursor as editable.
Related errors
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/8aadfa0363bffcaf.
Report an issue: GitHub.