SDK
JavaScript SDK
@graphcms/sdk: a typed client for REST and GraphQL.
@graphcms/sdk is the official JavaScript client. It wraps the REST and GraphQL APIs in typed methods, adds request timeouts, and turns failures into error classes you can branch on. It ships TypeScript definitions and runs anywhere with fetch: Node 20+, edge runtimes and the browser.
Install
npm install @graphcms/sdkConfigure
1import { GraphCMS } from '@graphcms/sdk';23const graph = new GraphCMS({4 apiKey: process.env.GRAPH_API_KEY, // required5 environment: 'production', // or 'staging'6});| Option | Default | Description |
|---|---|---|
apiKey | required | A graph_ key. Without one the constructor throws GraphValidationError. |
environment | production | staging sends X-Environment: staging on every request. See Environments. |
baseUrl | https://api.graphcms.io | Point it at http://localhost:3004 when running Graph locally. |
timeout | 30000 | Milliseconds before a request is aborted with GraphNetworkError. |
headers | {} | Extra headers for every request. |
debug | false | Log each request and response to the console. |
withConfig returns a new client with some options changed, which is handy for a second environment:
const staging = graph.withConfig({ environment: 'staging' });Reading content
The content methods use the REST API:
1// Every content type, with its fields2const types = await graph.content.types();34// One content type5const blog = await graph.content.type('blog-posts');67// A page of entries8const { data: posts } = await graph.content.entries('blog-posts', {9 limit: 20,10 offset: 0,11 status: 'published',12 locale: 'fr-FR',13});1415// One entry, or null16const post = await graph.content.entry('blog-posts', 'hello-world', { locale: 'fr-FR' });entries options are limit, offset, status and locale, passed straight through as query parameters.
In SDK 1.0.0, the
totalthatcontent.entriesreturns is always0: it reads a top-leveltotal, and the API reports it aspagination.total. If you need the total, usegraph.graphql.getContentEntries, which returns the real one.
GraphQL methods
graph.graphql runs GraphQL operations. It has ready-made queries:
1const types = await graph.graphql.getContentTypes();2const blog = await graph.graphql.getContentType('blog-posts');34const { entries, total } = await graph.graphql.getContentEntries('blog-posts', {5 status: 'published',6 limit: 10,7});89const post = await graph.graphql.getContentEntry('blog-posts', 'hello-world', { locale: 'de-DE' });Your own queries, with variables:
1const data = await graph.graphql.query({2 query: `3 query Recent($limit: Int) {4 contentEntries(contentType: "blog-posts", status: "published", limit: $limit) {5 entries { title slug publishedAt }6 }7 }8 `,9 variables: { limit: 3 },10});Writing content
Writes go through GraphQL, so the key needs the matching permissions:
1// content.edit (and content.publish to create it as published)2const entry = await graph.graphql.createContentEntry({3 contentTypeId: 'p8d2k4m9x0s1',4 title: 'Release notes for October',5 slug: 'release-notes-october',6 status: 'draft',7 fields: { excerpt: 'What shipped this month.' },8});910// content.edit, plus content.publish to change the published state11await graph.graphql.updateContentEntry(entry.id, { status: 'published' });1213// content.delete14await graph.graphql.deleteContentEntry(entry.id);Each throws an GraphError if the mutation returns success: false.
The SDK also has
graph.content.create,graph.content.update,graph.content.delete,graph.media.uploadandgraph.utils.health. In SDK 1.0.0 these call REST endpoints the API doesn't provide, so they fail. Use thegraph.graphqlmethods above for writes.
Utilities
1graph.utils.slugify('My Blog Post Title'); // 'my-blog-post-title'23// Check values against a content type before sending them. Fetch the type4// over GraphQL: its fields carry `validation`, which this check reads.5const type = await graph.graphql.getContentType('blog-posts');6const problems = graph.utils.validateFields({ excerpt: '' }, type);7// ['Field "Excerpt" is required']validateFields checks required fields, number/boolean value types and the min, max, minLength, maxLength and pattern rules. It runs locally and makes no request.
Errors
1import { GraphError, GraphAuthError, GraphNetworkError, GraphValidationError } from '@graphcms/sdk';23try {4 await graph.content.entry('blog-posts', 'hello-world');5} catch (error) {6 if (error instanceof GraphAuthError) {7 // 401, or a GraphQL "Unauthorized": the key is missing or wrong8 } else if (error instanceof GraphNetworkError) {9 // Timed out or couldn't connect: safe to retry10 } else if (error instanceof GraphValidationError) {11 // Bad configuration, such as no apiKey12 } else if (error instanceof GraphError) {13 // Any other API error: error.statusCode (REST), error.code, error.details14 if (error.statusCode === 403) console.error(error.message); // names the missing permission15 }16}| Class | code | When |
|---|---|---|
GraphAuthError | AUTH_ERROR | 401 from REST, or an Unauthorized GraphQL error. |
GraphNetworkError | NETWORK_ERROR | The request timed out or never got a response. |
GraphValidationError | VALIDATION_ERROR | The client was created without an apiKey. |
GraphError | API_ERROR, GRAPHQL_ERROR, CREATE_FAILED, … | Every other failure. All the classes above extend it. |
A 404 from content.entry or content.type is thrown as an GraphError with statusCode: 404, not returned as null.
In a framework
Create one client and reuse it. In Next.js, a server component:
1import { GraphCMS } from '@graphcms/sdk';23const graph = new GraphCMS({ apiKey: process.env.GRAPH_API_KEY });45export const revalidate = 60;67export default async function BlogPage() {8 const { data: posts } = await graph.content.entries('blog-posts', { limit: 10 });910 return (11 <main>12 {posts.map((post) => (13 <article key={post.id}>14 <h2>{post.title}</h2>15 <p>{post.fields.excerpt}</p>16 </article>17 ))}18 </main>19 );20}In a client-side app (React, Vue), call the SDK from your own backend rather than the browser, so the key stays private.
TypeScript
Types are exported for every object and option:
1import { GraphCMS, type ContentEntry, type ContentType, type ListOptions } from '@graphcms/sdk';23const graph = new GraphCMS({ apiKey: process.env.GRAPH_API_KEY! });4const { data }: { data: ContentEntry[] } = await graph.content.entries('blog-posts');