Graphdocs

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/sdk

Configure

1import { GraphCMS } from '@graphcms/sdk';
2
3const graph = new GraphCMS({
4 apiKey: process.env.GRAPH_API_KEY, // required
5 environment: 'production', // or 'staging'
6});
OptionDefaultDescription
apiKeyrequiredA graph_ key. Without one the constructor throws GraphValidationError.
environmentproductionstaging sends X-Environment: staging on every request. See Environments.
baseUrlhttps://api.graphcms.ioPoint it at http://localhost:3004 when running Graph locally.
timeout30000Milliseconds before a request is aborted with GraphNetworkError.
headers{}Extra headers for every request.
debugfalseLog 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 fields
2const types = await graph.content.types();
3
4// One content type
5const blog = await graph.content.type('blog-posts');
6
7// A page of entries
8const { data: posts } = await graph.content.entries('blog-posts', {
9 limit: 20,
10 offset: 0,
11 status: 'published',
12 locale: 'fr-FR',
13});
14
15// One entry, or null
16const 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 total that content.entries returns is always 0: it reads a top-level total, and the API reports it as pagination.total. If you need the total, use graph.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');
3
4const { entries, total } = await graph.graphql.getContentEntries('blog-posts', {
5 status: 'published',
6 limit: 10,
7});
8
9const 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});
9
10// content.edit, plus content.publish to change the published state
11await graph.graphql.updateContentEntry(entry.id, { status: 'published' });
12
13// content.delete
14await 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.upload and graph.utils.health. In SDK 1.0.0 these call REST endpoints the API doesn't provide, so they fail. Use the graph.graphql methods above for writes.

Utilities

1graph.utils.slugify('My Blog Post Title'); // 'my-blog-post-title'
2
3// Check values against a content type before sending them. Fetch the type
4// 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';
2
3try {
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 wrong
8 } else if (error instanceof GraphNetworkError) {
9 // Timed out or couldn't connect: safe to retry
10 } else if (error instanceof GraphValidationError) {
11 // Bad configuration, such as no apiKey
12 } else if (error instanceof GraphError) {
13 // Any other API error: error.statusCode (REST), error.code, error.details
14 if (error.statusCode === 403) console.error(error.message); // names the missing permission
15 }
16}
ClasscodeWhen
GraphAuthErrorAUTH_ERROR401 from REST, or an Unauthorized GraphQL error.
GraphNetworkErrorNETWORK_ERRORThe request timed out or never got a response.
GraphValidationErrorVALIDATION_ERRORThe client was created without an apiKey.
GraphErrorAPI_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:

app/blog/page.jsx
1import { GraphCMS } from '@graphcms/sdk';
2
3const graph = new GraphCMS({ apiKey: process.env.GRAPH_API_KEY });
4
5export const revalidate = 60;
6
7export default async function BlogPage() {
8 const { data: posts } = await graph.content.entries('blog-posts', { limit: 10 });
9
10 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';
2
3const graph = new GraphCMS({ apiKey: process.env.GRAPH_API_KEY! });
4const { data }: { data: ContentEntry[] } = await graph.content.entries('blog-posts');