Content model
How content in a Source CMS site is structured, and how each structure surfaces in the JSON:API. For querying instructions, see the querying guide.
Entity types and bundles
Section titled “Entity types and bundles”Every piece of content belongs to an entity type, and every entity type is divided into bundles (the bundle term varies by entity type). Machine names appear directly in endpoint paths and in the type member of every response ({entity_type}--{bundle}, for example node--article).
The model on one example bundle: scalar fields live under attributes; references to other entities live under relationships, one-to-one or one-to-many by the field’s cardinality; media chains on to its file entity:
| Entity type | Machine name | Bundle term | Endpoint pattern | Example |
|---|---|---|---|---|
| Content | node |
Content type | /api/node/{content_type} |
/api/node/article |
| Media | media |
Media type | /api/media/{media_type} |
/api/media/image |
| Taxonomy | taxonomy_term |
Vocabulary | /api/taxonomy/{vocabulary} |
/api/taxonomy/tags |
| Menu items | menu_items |
Menu | /api/menu_items/{menu} |
/api/menu_items/main |
One asymmetry: taxonomy term endpoints use the path segment taxonomy, not the entity type machine name; /api/taxonomy_term/tags is not a route. The type member in responses still reads taxonomy_term--tags. When in doubt, the API root index (GET $DRUPAL_SITE_URL/api) maps every resource type to its real path.
You can customize any bundle and create new ones, so the definitive list for your site is generated: check the API root (GET $DRUPAL_SITE_URL/api) or your site’s OpenAPI documentation (see the Content API reference).
Used in: query quickstart (finding the machine name), querying guide.
CMS content vs. Pages
Section titled “CMS content vs. Pages”Source CMS has two fundamental kinds of content, and they behave differently in the API:
| CMS content | Pages | |
|---|---|---|
| What it is | Standard content entities (articles, events, custom content types) with a predefined field structure | Entries built visually in Drupal Canvas |
| Focus | Reusable, structured data | Visual presentation |
| Reusability | High (used in multiple places, accessed through the API) | Low (typically unique per page) |
| Management | Data-centric | Layout-centric |
| Content workflows | Apply | Do not apply |
| API access | Served over JSON:API as node entities with their fields as attributes/relationships; the primary way CMS content reaches other channels |
Served as the page resource type at GET /api/page (no bundle segment; a fresh site’s /api/page lists its Homepage and error pages) |
The distinguishing characteristic is the resource type: a Page appears in the API as page; CMS content appears as node--{content_type}. Because workflows govern CMS content only, workflow state never gates a Canvas page; the writing content guide covers the write-side consequences.
Used in: writing content guide, Canvas components section.
Where fields appear in a response
Section titled “Where fields appear in a response”Each entry’s fields appear under two keys, using their machine names verbatim. Note on naming: fields created through Source CMS’s own tooling (the admin UI, the MCP server’s field tools) get unprefixed machine names (image, subtitle: verified), while the classic Drupal field_ prefix (field_image) appears on types built with other tooling. Never assume either form, read it from a response:
attributes: scalar and structured values stored on the entry itself.relationships: references to other entities (media, taxonomy terms, other content). A relationship contains type + id identifiers only; useincludeto embed the referenced entries (see the query parameter reference).
Field types → JSON:API response shapes
Section titled “Field types → JSON:API response shapes”The field types available in Source CMS and the shape each takes in a response:
| Field type | Appears under | JSON shape | Notes |
|---|---|---|---|
| Plain text | attributes |
"string" |
Single line, 255-character limit. |
| Formatted text | attributes |
{ "value": "...", "format": "...", "processed": "<p>...</p>" } |
Render processed (sanitized HTML); value is raw editor input: don’t render it directly. |
| Text (plain, long) | attributes |
"string" |
Multi-line, no formatting. |
| Number | attributes |
123 or "12.50" |
Integers are JSON numbers; decimal values are returned as strings to preserve precision. |
| Date and time | attributes |
"2026-07-02T09:30:00+00:00" |
ISO 8601, stored and returned in UTC; convert to the viewer’s timezone for display. Date-only fields return "2026-07-02". |
| Boolean | attributes |
true / false |
|
| Link | attributes |
{ "uri": "https://...", "title": "...", "options": [] } |
uri may also be an internal scheme; title is the optional link text. |
attributes |
"[email protected]" |
Validated on input. | |
| Selection list | attributes |
"option_key" (or an array when multiple selection is enabled) |
The stored key, not the display label. |
| Media | relationships |
{ "data": { "type": "media--image", "id": "<uuid>" } } |
References a media entity, which references its file via the media_image relationship (verified). To get a usable URL, include through to the file (e.g. include=image.media_image); the file’s uri.url is a root-relative path: prefix it with DRUPAL_SITE_URL. For images, the relationship’s meta carries alt, title, width, and height (verified); the file entity carries filename, filemime, and filesize. |
| Reference | relationships |
{ "data": { "type": "...", "id": "<uuid>" } } |
Points at content, media, taxonomy terms, or users, per the field’s configuration. |
Fields configured to allow multiple values return arrays: a multi-value attribute is an array of the shapes above, and a multi-value relationship’s data is an array of identifiers.
Used in: querying guide, direct-fetch guide.
Relationship shapes: one-to-one vs. one-to-many
Section titled “Relationship shapes: one-to-one vs. one-to-many”Whether data is an object or an array is fixed by the field’s cardinality:
"relationships": { "image": { "data": { "type": "media--image", "id": "9a2f..." } }, "tags": { "data": [ { "type": "taxonomy_term--tags", "id": "1c7e..." }, { "type": "taxonomy_term--tags", "id": "5b03..." } ] }}- One-to-one (single-value field):
datais one identifier object, ornullwhen empty. - One-to-many (multi-value field):
datais an array,[]when empty.
Identifiers alone are rarely enough: resolving them one request at a time is the N+1 pitfall. The querying guide shows the single-request include alternative.
Used in: querying guide.
Translated fields
Section titled “Translated fields”Content translation produces per-language versions of an entry: the translatable fields differ per language while the entry keeps its identity, and every response carries the entry’s language verbatim in its langcode attribute. A translation is requested with the langCode query parameter, provided by the JSON:API Multilingual module that every Source CMS site ships. The parameter is strict, so a missing translation is a 404 naming the langcodes that do exist. Adding includeFallback=1 opts the read into the site’s language fallback chain. A langcode URL path prefix resolves but is not a reliable way to select the content language. For requesting a specific language and detecting fallback, see the multilingual guide. Non-translatable fields carry the same value in every language; that is shared data, not a fallback signal.
Used in: multilingual guide.
Used in
Section titled “Used in”- Content API quickstart: machine name → endpoint mapping
- Querying guide: field shapes, relationships, includes
- Direct-fetch guide: rendering field shapes in a frontend
- Multilingual content guide: translated fields
- Creating & updating content via the API: CMS content vs. Pages and workflow scope
Was this page helpful?
What went wrong?
Still stuck? Contact Acquia Support (opens in a new tab)