Graphdocs

GraphQL

Queries

Content types, entry lists and single entries.

All queries need the content.view permission (except apiKeys, which needs api_keys.manage) and run against the environment the request asks for.

Content types

1query ContentTypes {
2 contentTypes {
3 id
4 name
5 slug
6 description
7 isActive
8 fields {
9 name
10 slug
11 fieldType
12 isRequired
13 order
14 }
15 }
16}

Returns every content type, newest first, each with its fields in order. Fetch a single one by slug or ID; both return null if it doesn't exist:

1query BlogPostType {
2 contentType(slug: "blog-posts") {
3 id
4 name
5 fields { slug fieldType validation fieldOptions defaultValue }
6 }
7}
1query TypeById {
2 contentTypeById(id: "p8d2k4m9x0s1") { name slug }
3}

ContentType fields: id, teamId, name, slug, description, icon, isActive, fields, createdAt, updatedAt. (color is still in the schema for older clients but is always null.)

ContentField fields: id, contentTypeId, name, slug, fieldType, description, isRequired, defaultValue (a string; JSON values are stringified), fieldOptions, validation, order, createdAt, updatedAt.

Entry lists

1query BlogPosts($limit: Int, $offset: Int) {
2 contentEntries(
3 contentType: "blog-posts"
4 status: "published"
5 limit: $limit
6 offset: $offset
7 ) {
8 entries {
9 id
10 title
11 slug
12 fields
13 publishedAt
14 author { firstName lastName }
15 }
16 total
17 limit
18 offset
19 }
20}
ArgumentDefaultDescription
contentTyperequiredThe content type's slug. An unknown slug is a NOT_FOUND error.
statusanypublished, draft or archived. Without it, every status is returned.
languageanyOnly entries written in this language.
localeen-USReturn translated text fields in this locale.
limit101 to 100.
offset0Entries to skip.

Entries come newest first. total counts every match, not just this page.

REST's /v1/content/:type returns only published entries by default; GraphQL's contentEntries returns all of them unless you pass status. A public site should always pass status: "published".

A single entry

By content type and slug:

1query Post($slug: String!) {
2 contentEntry(contentType: "blog-posts", slug: $slug, locale: "fr-FR") {
3 id
4 title
5 status
6 stage { id name }
7 locale
8 fields
9 contentType {
10 name
11 fields { slug fieldType }
12 }
13 }
14}

Or by ID:

1query PostById {
2 contentEntryById(id: "k3x9m2p7q1a4") {
3 title
4 slug
5 status
6 }
7}

Both return null when nothing matches, and both return the entry whatever its status. contentEntry also takes language to pick one language version.

ContentEntry fields: id, contentTypeId, contentType, teamId, title, slug, status, language, locale, authorId, author, fields, publishedAt, createdAt, updatedAt.

author is a User with id, firstName, lastName and email (companyName is still accepted for older queries but is no longer collected, so it's usually null). For an entry created through the API, the author is the person who created the API key.

API keys

Lists the workspace's keys. Needs api_keys.manage. The keys themselves are never returned, only a short preview.

1query Keys {
2 apiKeys {
3 id
4 name
5 keyPreview
6 permissions
7 isActive
8 lastUsedAt
9 createdAt
10 }
11}

Variables

Pass values as variables rather than building query strings, so nothing needs escaping:

1const query = `
2 query Post($slug: String!, $locale: String) {
3 contentEntry(contentType: "blog-posts", slug: $slug, locale: $locale) {
4 title
5 fields
6 }
7 }
8`;
9
10const response = await fetch('https://api.graphcms.io/graphql', {
11 method: 'POST',
12 headers: {
13 Authorization: `Bearer ${process.env.GRAPH_API_KEY}`,
14 'Content-Type': 'application/json',
15 },
16 body: JSON.stringify({ query, variables: { slug: 'hello-world', locale: 'de-DE' } }),
17});
18
19const { data } = await response.json();