toeverything/AFFiNE · error · InvalidIndexerInput
invalid_indexer_input
invalid_indexer_input
Error message
Invalid indexer input: ${reason} What it means
While building the search/aggregate DSL, the indexer service rejects pagination.limit > 10000 with InvalidIndexerInput (invalid_input, reason 'limit must be less than 10000'). The cap protects the search backend from oversized result sets and deep pagination.
Source
Thrown at packages/backend/server/src/plugins/indexer/service.ts:147
if (!doc.title) doc.title = titles.get(doc.docId) ?? '';
}
}
const users = await this.models.user.getPublicUsersMap(userIds);
for (const doc of docs) {
doc.createdByUser = users.get(doc.createdByUserId);
doc.updatedByUser = users.get(doc.updatedByUserId);
}
return docs;
}
private unwrap<T>(output: SearchOperationOutput, workspaceId: string): T {
if (output.ok) return output.value as T;
switch (output.errorCode) {
case 'workspace_denied':
throw new SpaceAccessDenied({ spaceId: workspaceId });
case 'invalid_request':
case 'unsupported_query':
throw new InvalidIndexerInput({ reason: output.errorCode });
case 'provider_unavailable':
throw new SearchProviderNotFound();
case 'permission_unavailable':
throw new WorkspacePermissionNotFound({ spaceId: workspaceId });
default:
throw new InternalServerError();
}
}
}
View on GitHub (pinned to 591f874dad)
Solutions
- Clamp the limit to 10000 (or omit it entirely to use the default page size)
- Use pagination.cursor for iterating large result sets instead of one big limit
- For exports, loop with skip/cursor and accumulate results
Example fix
// before
const dsl = service.search({ table, query, options: { pagination: { limit: totalCount } } });
// after
const MAX = 10000;
const dsl = service.search({ table, query, options: { pagination: { limit: Math.min(totalCount, MAX) } } }); Defensive patterns
Strategy: validation
Validate before calling
const MAX_LIMIT = 10000;
const limit = Math.min(requestedLimit ?? DEFAULT_PAGE, MAX_LIMIT);
const dsl = service.search({ table, query, options: { pagination: { limit } } }); Type guard
const isValidLimit = (limit: number | undefined): boolean => limit === undefined || (Number.isInteger(limit) && limit <= 10000 && limit > 0);
Try / catch
try {
return await search(input);
} catch (e) {
if (e?.code === 'invalid_indexer_input' && /limit/.test(e.reason)) {
input.options.pagination.limit = 10000;
return search(input);
}
throw e;
} Prevention
- Never pass a total count as pagination.limit; clamp inputs at the API boundary
- Default to cursor pagination for anything that may exceed 10000 rows
- Centralize limit constants shared between client and server
When it happens
Trigger: Passing options.pagination.limit greater than 10000 to the indexer search or aggregate APIs - typically code that passes a total count (e.g. docs count from another query) straight in as the limit.
Common situations: 'Fetch everything' export code paths; clients computing limit = totalCount; migrating from an API that had no cap.
Related errors
- search_provider_not_found
- workspace_permission_not_found
- internal_server_error
- query_too_long
- query_too_long
AI-assisted analysis of toeverything/AFFiNE@591f874dad (2026-08-21).
Data as JSON: /api/errors/58821102ffbe69b5.
Report an issue: GitHub.