Implement cursor-based or offset pagination for Prisma queries. Use for datasets 100k+, APIs with page navigation, or infinite scroll/pagination mentions.
Implements cursor or offset pagination for Prisma queries. Triggers on mentions of pagination, infinite scroll, or large datasets (100k+), guiding strategy selection and providing code patterns for performance.
/plugin marketplace add djankies/claude-configs/plugin install prisma-6@claude-configsThis skill is limited to using the following tools:
references/api-implementation-examples.mdreferences/bidirectional-pagination.mdreferences/common-mistakes.mdreferences/data-change-handling.mdreferences/performance-comparison.mdTeaches correct Prisma 6 pagination patterns with guidance on cursor vs offset trade-offs and performance implications.
<role> Implement cursor-based or offset-based Prisma pagination strategies, choosing based on dataset size, access patterns, and performance requirements. </role> <when-to-activate> Activates when: user mentions "pagination," "page," "infinite scroll," "load more"; building APIs with page navigation/list endpoints; optimizing large datasets (100k+) or slow queries; implementing table/feed views. </when-to-activate> <overview> **Cursor-based pagination** (recommended): Stable performance regardless of size; efficient for infinite scroll; handles real-time changes gracefully; requires unique sequential ordering field.Offset-based pagination: Simple; supports arbitrary page jumps; degrades significantly on large datasets (100k+); prone to duplicates/gaps during changes.
**Core principle: Default to cursor. Use offset only for
small (<10k), static datasets requiring arbitrary page access.** </overview>
<workflow> ## Pagination Strategy WorkflowPhase 1: Choose Strategy
Phase 2: Implement
Phase 3: Optimize & Validate
| Criterion | Cursor | Offset | Winner |
|---|---|---|---|
| Dataset > 100k | Stable O(n) | O(skip+n) | Cursor |
| Infinite scroll | Natural | Poor | Cursor |
| Page controls (1,2,3...) | Workaround needed | Natural | Offset |
| Jump to page N | Not supported | Supported | Offset |
| Real-time data | No duplicates | Duplicates/gaps | Cursor |
| Total count needed | Extra query | Same query | Offset |
| Complexity | Medium | Low | Offset |
| Mobile feed | Natural | Poor | Cursor |
| Admin table (<10k) | Overkill | Simple | Offset |
| Search results | Good | Acceptable | Cursor |
Guidelines: (1) Default cursor for user-facing lists; (2) Use offset only for small admin tables, total-count requirements, or arbitrary page jumping in internal tools; (3) Never use offset for feeds, timelines, >100k datasets, infinite scroll, real-time data. </decision-matrix>
<cursor-pagination> ## Cursor-Based PaginationCursor pagination uses a pointer to a specific record as the starting point for the next page.
async function getPosts(cursor?: string, pageSize: number = 20) {
const posts = await prisma.post.findMany({
take: pageSize,
skip: cursor ? 1 : 0,
cursor: cursor ? { id: cursor } : undefined,
orderBy: { id: 'asc' },
});
return {
data: posts,
nextCursor: posts.length === pageSize ? posts[posts.length - 1].id : null,
};
}
For non-unique fields (createdAt, score), combine with unique field:
async function getPostsByDate(cursor?: { createdAt: Date; id: string }, pageSize: number = 20) {
const posts = await prisma.post.findMany({
take: pageSize,
skip: cursor ? 1 : 0,
cursor: cursor ? { createdAt_id: cursor } : undefined,
orderBy: [{ createdAt: 'desc' }, { id: 'asc' }],
});
const lastPost = posts[posts.length - 1];
return {
data: posts,
nextCursor:
posts.length === pageSize ? { createdAt: lastPost.createdAt, id: lastPost.id } : null,
};
}
Schema requirement:
model Post {
id String @id @default(cuid())
createdAt DateTime @default(now())
@@index([createdAt, id])
}
Offset pagination skips a numeric offset of records.
async function getPostsPaged(page: number = 1, pageSize: number = 20) {
const skip = (page - 1) * pageSize;
const [posts, total] = await Promise.all([
prisma.post.findMany({ skip, take: pageSize, orderBy: { createdAt: 'desc' } }),
prisma.post.count(),
]);
return {
data: posts,
pagination: { page, pageSize, totalPages: Math.ceil(total / pageSize), totalRecords: total },
};
}
Complexity: Page 1 O(pageSize); Page N O(N×pageSize)—linear degradation
Real-world example (1M records, pageSize 20):
Database must scan and discard skipped rows despite indexes.
Use only when: (1) dataset <10k OR deep pages rare; (2) arbitrary page access required; (3) total count needed; (4) infrequent data changes. Common cases: admin tables, search results (rarely past page 5), static archives. </offset-pagination>
<validation> ## ValidationIndex verification: Schema has index on ordering field(s); for cursor use @@index([field1, field2]); run npx prisma format
Performance testing:
console.time('First page');
await getPosts(undefined, 20);
console.timeEnd('First page');
console.time('Page 100');
await getPosts(cursor100, 20);
console.timeEnd('Page 100');
Cursor: both ~similar (5–50ms); Offset: verify acceptable for your use case
Edge cases: first page, last page (<pageSize results), empty results, invalid cursor/page, concurrent modifications
API contract: response includes pagination metadata; nextCursor null when done; hasMore accurate; page numbers
validated (>0); consistent ordering across pages; unique fields in composite cursors </validation>
<constraints> **MUST**: Index cursor field(s); validate pageSize (max 100); handle empty results; return pagination metadata; use consistent ordering; include unique fields in composite cursorsSHOULD: Default cursor for user-facing lists; limit offset to <100k datasets; document pagination strategy; test realistic sizes; consider caching total count
NEVER: Use offset for >100k datasets, infinite scroll, feeds/timelines, real-time data; omit indexes; allow unlimited pageSize; use non-unique sole cursor; modify ordering between requests </constraints>
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.