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".
Aller à l'installation Skills Marketplace Découvrez et explorez les compétences IA créées par la communauté.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Copier le promptAfficher les détails du prompt Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
npx skills add https://github.com/jeremylongshore/claude-code-plugins-plus-skills --skill linear-sdk-patternsLa commande reste sur une seule ligne. Faites défiler horizontalement pour la vérifier avant de la copier.
Vous préférez une copie locale ? Téléchargez les fichiers actuellement disponibles dans SkillsMP.
Télécharger Zip Téléchargement... Métiers associés SOC
Basé sur la classification professionnelle SOC
Explorateur de fichiers
2 fichiers 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