Skip to main content
linear-sdk-patterns TypeScript/JavaScript SDK patterns and best practices for Linear.
Use when learning SDK idioms, implementing pagination,
filtering, relation loading, or custom GraphQL queries.
Trigger: "linear SDK patterns", "linear best practices",
"linear typescript", "linear API patterns", "linear pagination".
Ir a la instalación Skills Marketplace Descubre y explora habilidades de IA creadas por la comunidad.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Copiar promptMostrar detalles del prompt Un comando directo omite el prompt de revisión. Revisa el origen antes de ejecutarlo.
npx skills add https://github.com/jeremylongshore/claude-code-plugins-plus-skills --skill linear-sdk-patternsEl comando permanece en una sola línea. Desplázate horizontalmente para revisarlo antes de copiarlo.
¿Prefieres una copia local? Descarga los archivos que SkillsMP tiene disponibles ahora.
Descargar Zip Descargando... Explorador de archivos
2 archivos Ocupaciones relacionadas SOC
Basado en la clasificación ocupacional SOC
name linear-sdk-patterns description TypeScript/JavaScript SDK patterns and best practices for Linear.
Use when learning SDK idioms, implementing pagination,
filtering, relation loading, or custom GraphQL queries.
Trigger: "linear SDK patterns", "linear best practices",
"linear typescript", "linear API patterns", "linear pagination".
allowed-tools Read, Write, Edit, Grep version 1.12.0 license MIT author Jeremy Longshore <jeremy@intentsolutions.io> tags ["saas","linear","api","typescript"] compatibility Designed for Claude Code, also compatible with Codex and OpenClaw
Linear SDK Patterns
Overview
Production patterns for @linear/sdk. The SDK wraps Linear's GraphQL API with strongly-typed models, cursor-based pagination (fetchNext()/fetchPrevious()), lazy-loaded relations, and typed error classes. Understanding these patterns avoids N+1 queries and rate limit waste.
Prerequisites
@linear/sdk installed
TypeScript project with strict: true
Understanding of async/await and GraphQL concepts
Instructions
Pattern 1: Client Singleton
import { LinearClient } from "@linear/sdk" ;
let _client : LinearClient | null = null ;
export function getLinearClient ( ): LinearClient {
if (!_client) {
const apiKey = process.env .LINEAR_API_KEY ;
if (!apiKey) throw new Error ("LINEAR_API_KEY is required" );
_client = new LinearClient ({ apiKey });
}
return _client;
}
const clientCache = new Map <string , LinearClient >();
export function getClientForUser (userId : string , : ): {
(!clientCache. (userId)) {
clientCache. (userId, ({ accessToken }));
}
clientCache. (userId)!;
}
accessToken
string
LinearClient
if
has
set
new
LinearClient
return
get
Pattern 2: Cursor-Based Pagination Linear uses Relay-style cursor pagination. The SDK provides fetchNext() and fetchPrevious() helpers, plus raw pageInfo for manual control.
const firstPage = await client.issues ({ first : 50 });
console .log (`Page 1: ${firstPage.nodes.length} issues` );
if (firstPage.pageInfo .hasNextPage ) {
const secondPage = await firstPage.fetchNext ();
console .log (`Page 2: ${secondPage.nodes.length} issues` );
}
async function * paginateAll<T>(
fetchPage : (cursor ?: string ) => Promise <{
nodes : T[];
pageInfo : { hasNextPage : boolean ; endCursor : string };
}>
): AsyncGenerator <T> {
let cursor : string | undefined ;
let hasNext = true ;
while (hasNext) {
const page = await fetchPage (cursor);
for (const node of page.nodes ) yield node;
hasNext = page.pageInfo .hasNextPage ;
cursor = page.pageInfo .endCursor ;
}
}
for await (const issue of paginateAll (c => client.issues ({ first : 50 , after : c }))) {
console .log (`${issue.identifier} : ${issue.title} ` );
}
Pattern 3: Relation Loading (Avoiding N+1) SDK models lazy-load relations. Accessing .assignee triggers a separate API call. Use raw GraphQL to batch-fetch relations in one request.
const issues = await client.issues ({ first : 50 });
for (const issue of issues.nodes ) {
const assignee = await issue.assignee ;
console .log (`${issue.identifier} : ${assignee?.name} ` );
}
const response = await client.client .rawRequest (`
query TeamIssues($teamKey: String!) {
issues(first: 50, filter: { team: { key: { eq: $teamKey } } }) {
nodes {
id identifier title priority
assignee { name email }
state { name type }
labels { nodes { name color } }
project { name }
}
}
}
` , { teamKey : "ENG" });
async function enrichIssue (issue : any ) {
const [assignee, state, team, labels] = await Promise .all ([
issue.assignee ,
issue.state ,
issue.team ,
issue.labels (),
]);
return { ...issue, _assignee : assignee, _state : state, _team : team, _labels : labels.nodes };
}
Pattern 4: Filtering with Comparators Linear supports eq, neq, in, nin, lt, lte, gt, gte, startsWith, contains, and logical and/or operators.
const bugs = await client.issues ({
first : 50 ,
filter : {
priority : { lte : 2 },
state : { type : { nin : ["completed" , "canceled" ] } },
labels : { name : { eq : "Bug" } },
team : { key : { eq : "ENG" } },
},
});
const filtered = await client.issues ({
filter : {
or : [
{ assignee : { email : { eq : "alice@company.com" } } },
{ assignee : { email : { eq : "bob@company.com" } } },
],
state : { type : { eq : "started" } },
},
});
const results = await client.issueSearch ("authentication bug" );
const recent = await client.issues ({
filter : {
updatedAt : { gte : new Date (Date .now () - 24 * 60 * 60 * 1000 ).toISOString () },
},
orderBy : "updatedAt" ,
first : 100 ,
});
Pattern 5: Type-Safe Error Handling import { LinearError , InvalidInputLinearError } from "@linear/sdk" ;
type Result <T> = { ok : true ; data : T } | { ok : false ; error : string ; retryable : boolean };
async function safeCall<T>(fn : () => Promise <T>): Promise <Result <T>> {
try {
return { ok : true , data : await fn () };
} catch (error) {
if (error instanceof InvalidInputLinearError ) {
return { ok : false , error : `Invalid input: ${error.message} ` , retryable : false };
}
if (error instanceof LinearError ) {
const retryable = error.status === 429 || error.status === 503 ;
return { ok : false , error : `[${error.status} ] ${error.message} ` , retryable };
}
return { ok : false , error : String (error), retryable : false };
}
}
const result = await safeCall (() => client.issue ("issue-uuid" ));
if (result.ok ) {
console .log (result.data .title );
} else if (result.retryable ) {
console .warn ("Transient error, retry:" , result.error );
}
Pattern 6: Custom GraphQL Client Access the underlying LinearGraphQLClient for full control.
const graphQLClient = client.client ;
graphQLClient.setHeader ("X-Request-Id" , crypto.randomUUID ());
const data = await graphQLClient.rawRequest (`
query Cycle($id: String!) {
cycle(id: $id) {
id name startsAt endsAt
issues { nodes { identifier title state { name } } }
}
}
` , { id : "cycle-uuid" });
const batchResult = await graphQLClient.rawRequest (`
mutation BatchUpdate {
a: issueUpdate(id: "id1", input: { priority: 1 }) { success }
b: issueUpdate(id: "id2", input: { priority: 1 }) { success }
c: issueUpdate(id: "id3", input: { priority: 1 }) { success }
}
` );
Error Handling Error Cause Solution Cannot read properties of nullNullable relation not checked Use (await issue.assignee)?.name Type is not assignableSDK/TypeScript version mismatch Update @linear/sdk to latest Promise rejection unhandledMissing try/catch on async Wrap in safeCall() or .catch() Query complexity too highToo many nested relations Use rawRequest() with flat field selection
Examples
Create Issue with Full Metadata const teams = await client.teams ();
const eng = teams.nodes .find (t => t.key === "ENG" )!;
const states = await eng.states ();
const todo = states.nodes .find (s => s.type === "unstarted" )!;
const labels = await client.issueLabels ({ filter : { name : { eq : "Bug" } } });
await client.createIssue ({
teamId : eng.id ,
title : "Login page crashes on Safari" ,
description : "## Steps to reproduce\n1. Open login in Safari 17\n2. Click Sign in\n3. Crash" ,
stateId : todo.id ,
priority : 1 ,
labelIds : [labels.nodes [0 ].id ],
estimate : 3 ,
});
Resources