The Craft CMS team have (as of May 1st 2025) released CKEditor 4.8.0, and it now provides a way to access longform content (content alongside nested entries) from a single GraphQL query.
Prior to 4.8.0, the CKEditor plugin would only return the raw HTML, and if you wanted to include nested entries within the field, you had to do one of the following:
- Make partials in Twig, these would then be parsed by Craft CMS and send through the rendered code to the front-end. (Bad, as it's not dynamic, can't be altered easily with the front-end).
- Parse the returned data with an Entry ID (written in a partial) and re-request the data to fetch each entry. (Bad, as it requires more requests).
- Turn the CKEditor field into a matrix and provide different options this way (Bad, as it provides a degraded user experience for content editors).
None of these were really ideal solutions, especially when working on a site that had already been previously coded and wanted a couple of entries dropping in.
So, after trying each solution and scouring the web, I dropped a post on Mastadon, Brandon replied that he'd be working on a solution, and not even a week later we have a new update where we have multitude of ways we can parse the data nicely for our front-end. Shout out to Brandon for the release of this one so quickly.
Updating your old fields
If you're using CKEditor fields that were created prior to 4.8.0, you'll have to make a quick tweak to them that allows the data to be output in different ways. To do this, edit your field in the Craft CMS admin, and under "Advanced", change the GraphQL mode to "Full Data.".
For any new fields created, they will be set to "Full Data" by default.
Fetching data the old way
If you still want to return all the data similar to before 4.8.0, you can set the field GraphQL Mode to "Raw content only", and then access it the same as before.
query {
entries(section: "mySection") {
...on myEntryType_Entry {
myCkeditorField
}
}
}
Or, with the field set to "Full Data", run a query for the rawHtml
.
query {
entries(section: "mySection") {
...on myEntryType_Entry {
myCkeditorField {
rawHtml
}
}
}
}
Fetching longform content
If you want to expand the way you query the data, there are now several ways we can access the output, whether that's html
, rawHtml
, markdown
or plainText
. However, we want to have a bit more granular control in our front-end, so we are going to fetch all the content in "Chunks" and then request the appropriate fields for each entry type.
To do this, we'll set up our base query like above, but this time fetching each chunk.
query {
entries(section: "mySection") {
...on myEntryType_Entry {
myCkeditorField {
chunks {
...on CkeditorMarkup {
__typename
html
}
...on myNestedEntryType_Entry {
__typename
# add your fields...
}
}
}
}
}
}
In the above, we have a new Inline Fragment (CkeditorMarkup
) that lets us grab the data in the various new formats. Alongside that, we have with a custom nested entry type that we're referencing.
For example, If we had data that had the following structure:
- Heading 1
- Paragraph 1
- Nested Entry
- Paragraph 2
We would get 3 chunks output in our GraphQL query, the first being the "Heading 1 + Paragraph 1" combined, then "Nested Entry", and the last the remaining "Paragraph 2". We can then request all of our custom fields in the Nested Entry, and ta-da, we have easily controllable longform content that we can then pass in to our front-end of choice.
An example of this can be shown below where I request the fields from a single entry.
{
"data": {
"articlesEntries": [
{
"id": "597",
"title": "Using View Transitions with Next.js",
"postDate": "2025/04/25",
"rte": {
"chunks": [
{
"__typename": "CkeditorMarkup",
"html": "<h1>Heading 1</h1><p>test</p>",
"rawHtml": "<h1>Heading 1</h1><p>test</p>",
"markdown": "Heading 1\n=========\n\ntest",
"plainText": "Heading 1\n\ntest"
},
{
"__typename": "codeBlock_Entry",
"fileName": "next.config.js",
"code": {
"language": "javascript",
"value": "/** @type {import('next').NextConfig} */\r\nconst nextConfig = {\r\n experimental: {\r\n viewTransition: true,\r\n },\r\n}\r\n \r\nmodule.exports = nextConfig"
}
},
{
"__typename": "CkeditorMarkup",
"html": "<p>test data</p>",
"rawHtml": "<p>test data</p>",
"markdown": "test data",
"plainText": "test data"
}
]
}
}
]
}
}