Graphdocs

Content API (REST)

Content types

The models your entries follow, with every field definition.

Content types are the models your entries follow. The REST API exposes them read-only, with every field definition, so your code can discover the shape of your content. To create or change content types from code, use the GraphQL mutations.

Both endpoints need the content.view permission and accept ?environment=staging.

List content types

GET /v1/content-types
curl https://api.graphcms.io/v1/content-types \
-H "Authorization: Bearer $GRAPH_API_KEY"

Returns every content type in the environment, each with its fields in display order:

{
"success": true,
"data": [
{
"id": "p8d2k4m9x0s1",
"name": "Blog posts",
"slug": "blog-posts",
"description": "Articles for the company blog",
"icon": "file-text",
"isPublished": true,
"createdAt": "2026-09-01T10:00:00.000Z",
"updatedAt": "2026-09-12T08:31:00.000Z",
"fields": [
{
"id": "f1a2b3c4d5e6",
"name": "Excerpt",
"slug": "excerpt",
"fieldType": "textarea",
"description": "Shown on the blog index",
"isRequired": true,
"isUnique": false,
"isSearchable": true,
"defaultValue": null,
"validationRules": { "maxLength": 280 },
"fieldOptions": null,
"orderIndex": 0
}
]
}
]
}

Get one content type

GET /v1/content-types/:slug
curl https://api.graphcms.io/v1/content-types/blog-posts \
-H "Authorization: Bearer $GRAPH_API_KEY"

Returns { "success": true, "data": { ... } } with the same shape as one item above, or 404 with { "error": "Content type not found" } if no type has that slug in the environment.

Query parameters

ParameterDescription
environmentproduction (default) or staging. Also accepted as an X-Environment header.

The content type object

PropertyTypeDescription
idstring12-character ID.
namestringDisplay name.
slugstringIdentifier used in entry URLs, such as /v1/content/blog-posts.
descriptionstring or nullOptional description.
iconstring or nullIcon name chosen in the dashboard.
isPublishedbooleanWhether the type is active. GraphQL calls this isActive.
fieldsarrayField definitions, ordered by orderIndex.
createdAt, updatedAtISO 8601 stringTimestamps.

The field object

PropertyTypeDescription
idstring12-character ID.
namestringDisplay name.
slugstringThe key this field's value has in an entry's fields object.
fieldTypestringOne of text, textarea, html, datetime, color, media, reference, number, boolean, select, json, repeater, section.
descriptionstring or nullHelp text shown to editors.
isRequiredbooleanEditors must fill it in.
isUniquebooleanNo two entries may share a value.
isSearchablebooleanIncluded in dashboard search.
defaultValueanyValue used when an entry doesn't set one.
validationRulesobject or nullRules such as minLength, maxLength, min, max or pattern. GraphQL calls this validation.
fieldOptionsobject or nullType-specific options, such as a select's choices or a reference's targets (see below).
orderIndexnumberPosition in the editor. GraphQL calls this order.

Only text and textarea fields can be translated; see Localization.

Select fields

A select field holds one choice. Its options come from a list, or from another content type. Its fieldOptions:

OptionDescription
sourcelist (the default) or content.
optionsFor a list: { "label", "value" } objects in order. The label is what editors see; the value is what's stored and returned.
contentTypeFor content: the content type whose entries supply the options, by ID.
fieldFor content: the field whose values become the options, by slug, or __title for the entries' titles.

The field's defaultValue is the option new entries start with.

In an entry's fields, a select is its value as a string, e.g. "guides". Setting a list select to a value that isn't one of its options returns an error. A select that takes its options from another content type accepts any text, since those options change as that type's entries do.

Date & Time fields

A datetime field holds one date, or a range from one date to another. Its fieldOptions:

OptionDescription
modesingle (the default) or range.
includeTimefalse for a date without a time. Defaults to true.
formatHow editors see the date: medium (Oct 31, 2026), long (October 31, 2026), us (10/31/2026), eu (31/10/2026) or iso (2026-10-31). It doesn't change the API.
timeFormat12h (1:30 PM, the default) or 24h (13:30), for editors.
min, maxThe earliest and latest allowed day, as YYYY-MM-DD.

The API always returns ISO 8601. A single date is a string; a range is an object:

{
"publishedOn": "2026-10-31T13:30:00.000Z",
"runs": { "from": "2026-11-02T00:00:00.000Z", "to": "2026-11-06T00:00:00.000Z" }
}

A date without a time is stored at midnight UTC of that day, so read it as a calendar date (its date part) rather than converting it to a local time.

To set one, send the same shape: an ISO string, or { "from": …, "to": … } for a range (a single date for a range field is taken as its start). A date outside min/max, a range that ends before it starts, or a range for a single-date field returns an error.

Media fields

A media field holds the file chosen from the media library: an object with its id, url, originalName, mimeType and size. A repeater's media sub-fields hold the same object in each item.

When a file is deleted from the media library, it's removed from every entry that uses it, in both environments: the field (or the repeater item's sub-field) becomes null, so your frontend never gets a link to a file that's gone. The rest of the entry is unchanged.

Repeater fields

A repeater field holds a list of items, each with a value for every sub-field. Its fieldOptions:

OptionDescription
subFieldsThe sub-fields each item has: objects with name, slug and fieldType (text, textarea, number, boolean, datetime, select or media).
sortabletrue lets editors drag items into a new order.

In an entry's fields, a repeater is an array of objects keyed by sub-field slug, in the order editors set:

{ "slides": [{ "heading": "Alpha", "visible": true }, { "heading": "Beta", "visible": false }] }

Section fields

A section field only lays out the editor: it starts a new section on the entry form, with its name as the heading and its description underneath, and the fields after it (up to the next section) sit in it. It never holds a value, so it doesn't appear in an entry's fields. It does appear in a content type's field list, in order, so you can mirror the grouping in your own tools.

Reference fields

A reference field points at other entries: one, or several in a set order. Its fieldOptions say which:

{ "contentTypes": ["p8d2k4m9x0s1"], "multiple": true }
OptionDescription
contentTypesThe content types it can point at, by ID. Empty means any type. A type keeps the same ID here in staging and production.
multipletrue for a list of entries in order, false (the default) for one entry.

In an entry's fields, a single reference comes back as the entry it points at, or null. A multiple reference comes back as a list, in the order editors set:

{
"author": { "id": "d4fsmgijxvlb", "title": "Ada Lovelace", "slug": "ada-lovelace", "status": "published", "contentType": "authors" },
"related": [
{ "id": "k1camy6f2vyr", "title": "Getting started", "slug": "getting-started", "status": "published", "contentType": "blog-posts" }
]
}

Each referenced entry comes from the same environment as the entry that points at it. An entry that's been deleted is left out. To get a referenced entry's own fields, request it by its type and slug.

To set a reference when creating or updating an entry, give entry IDs: one ID (or a list with one) for a single reference, a list for a multiple one. null or [] clears it. An ID that doesn't exist, an entry of a type the field doesn't allow, or more than one entry for a single reference returns an error.

Import and export

In the dashboard, Import / export on the content types page exports every content type as CSV or Excel (.xlsx), with one row per field. It also imports a CSV or .xlsx file.

The columns are Content type, Content type slug, Content type description, Field, Field slug, Field type, Required, Unique, Default value, Field description and Field options. Field type is the type's API name (text, media, reference, …) or its label. Field options are the field's options as JSON, with content types given by slug, so the file works in another project.

When you import, drop a file on the dialog or browse for one, then map its columns to these. Columns are mapped by name to start with, and you can change each mapping. A preview then shows what each row will do, and highlights the cells of rows that can't be imported, such as an unknown field type, with the reason. Only rows that are ready are imported. Rows with the same content type become one type, and a row with only a content type makes a type with no fields. A content type that already exists is skipped, or gets the fields it doesn't have yet if you turn on Add missing fields to content types that already exist.