Skip to main content
Generalmarkus41

Linear Pagination + Filtering

This skill should be used when fetching large result sets from Linear — cursor pagination, filter DSL, sorting, and complexity budgeting. Activates on "linear pagination", "linear filter", "linear cursor", "linear filtering".

Stars
12
Source
markus41/claude
Updated
2026-05-11
Slug
markus41--claude--linear-pagination-filtering
View on GitHubRaw SKILL.md

// install — copy + paste into any project

mkdir -p .claude/skills && curl -fsSL https://raw.githubusercontent.com/markus41/claude/HEAD/plugins/linear-orchestrator/skills/linear-pagination-filtering/SKILL.md -o .claude/skills/linear-pagination-filtering.md

Drops the SKILL.md into .claude/skills/linear-pagination-filtering.md. Works with Claude Code, Cursor, and any agent that loads SKILL.md files from .claude/skills/.

Linear Pagination + Filtering

References:

Cursor pagination

Linear uses opaque forward cursors. Pattern:

query Page($cursor: String, $filter: IssueFilter) {
  issues(first: 50, after: $cursor, filter: $filter, orderBy: updatedAt) {
    pageInfo { hasNextPage endCursor }
    nodes { id }
  }
}

first ranges 1-250. No last/before — Linear is forward-only. To paginate descending, use orderBy with the appropriate direction (e.g. createdAt).

Helper in lib/pagination.ts:

async function* paginateAll<T>(
  fetch: (cursor: string | null) => Promise<{ nodes: T[]; pageInfo: { hasNextPage: boolean; endCursor: string } }>
): AsyncGenerator<T> {
  let cursor: string | null = null;
  while (true) {
    const page = await fetch(cursor);
    yield* page.nodes;
    if (!page.pageInfo.hasNextPage) return;
    cursor = page.pageInfo.endCursor;
  }
}

Filter DSL

Filters are nested JSON trees:

filter: {
  and: [
    { state: { type: { eq: "started" } } },
    { priority: { lte: 2 } },
    { or: [
      { assignee: { isMe: { eq: true } } },
      { team: { key: { eq: "ENG" } } }
    ] },
    { labels: { some: { name: { in: ["bug", "regression"] } } } }
  ]
}

Comparison operators

Op Strings Numbers Booleans Dates
eq
neq
in / nin
contains / startsWith
lt / lte / gt / gte
null

Connection filters

  • some: { ... } — at least one related entity matches
  • every: { ... } — all related entities match
  • none: { ... } — no related entity matches

Common filter recipes

Open issues in current cycle assigned to me:

filter: {
  and: [
    { state: { type: { neq: "completed" } } },
    { cycle: { isActive: { eq: true } } },
    { assignee: { isMe: { eq: true } } }
  ]
}

Issues blocked by something:

filter: { hasRelatedRelations: { eq: true } }

Triage queue for ENG team older than 24h:

filter: {
  and: [
    { team: { key: { eq: "ENG" } } },
    { state: { type: { eq: "triage" } } },
    { createdAt: { lt: "2026-04-29T00:00:00Z" } }
  ]
}

Sorting

orderBy: updatedAt | createdAt | priority | manual. For complex sorts, fetch unsorted and sort client-side (only feasible for small result sets).

Complexity budgeting

Each filter clause adds to query complexity. Reduce by:

  • Selecting only needed fields
  • Using first: <smaller> on connections within nodes
  • Splitting one big query into two parallel smaller ones (Linear allows up to 4 in-flight)