Craft CMS 3.3 Deep Dive

Author Avatar
Written by Cory Zibell,
• 10 min read

Craft CMS powers the majority of our web projects and applications. As Enterprise Partners, we utilize pretty much every feature that Craft has to offer, and through modules and plugins add our own.

At the end of August, Craft 3.3 was released, and it brought with it some huge new features. Things like GraphQL and Headless mode make Craft a powerful competitor in the headless CMS space. And this comes on the coattails of Craft 3 and previous releases, which have brought thousands of changes and improvements to the platform.

Craft 3 saw features like Composer support, multi-site, image editing, asset previews, the debug toolbar, element queries, PostgreSQL support, and the plugin store.

Craft 3.1 brought many admin and config specific features, including Project Configs, soft deletes, and better ENV support.

Craft 3.2 was the first glimmer into Craft as a true Headless CMS, with the Headless Preview feature. Additional features included autosave, improved localization, element exporting, sortable custom fields, related element validation, and a brand new testing framework, among other things.

Craft 3.3 is a special release. It’s highly focused on Craft being a Headless CMS, with full GraphQL support (for Craft Pro). Just in time for Spooktober , I’m going to cover the new headless features, and how you can start using them right now.

What is a Headless Horseman CMS?

Before we get into these new features, let’s go over what a Headless CMS is. And, to explain a what a Headless CMS is, it’s best to start with what it’s not.

The CMS, or Content Management System, is a piece of software that allows a user to manage some form of Content using a Graphical User Interface. Normally this content is specifically going to be used on the Web. The first generations of CMS were very page-based. They organized all content around pages. If there was shared content (say for example a testimonial, or call to action), that appeared among multiple pages, it would have to be explicitly defined in the code, or manually added to each page in the CMS. This was the "First Age" of CMSs.

Eventually, many CMSs transitioned to organizing content in a more amorphous way. Content wasn’t just blog posts or pages. Craft CMS is structured like this explicitly. When you first install Craft, there are no Sections or Fields.

Most CMSs historically not only provided the means to manage the data that was stored, but some sort of presentation or rendering technology to allow the data to be output into HTML that could be consumed by browsers. Craft tackles this through the popular Twig templating engine for PHP. This esoteric content being rendered by the CMS is the "Middle Ages" of CMSs. We are in the tail end of this era right now.

As Web technology has continued to focus more and more on "front-end components," less markup is coming from the server (and therefore the CMSs). Sites like Twitter and Reddit are Single Page Applications. The application that is "in-charge" of rendering and displaying content is separate and distinct from the application that is "in-charge" of storing, organizing, and returning content.

This trend is continuing to become more popular across the Web. By keeping the front-end and back-end separate, it keeps codebases separate, and allows developers to hyper-focus on the most important pieces of the application. It also ensures that the front-end and backend are interchangeable. As long as the content structure remains the same, each part of the app can theoretically operate as a "black box" which works with it’s own data and communicates with each other through RESTful APIs.

What’s this GraphQL business?

Most API request over the Web today are structured in either XML or JSON. While there are some RPC standards like SOAP and JSON-RPC that provide more structured API calls, there needs to be additional abstraction to ensure that the API is efficient. Additionally, different types of data are harder to transfer in a traditional API, and require a more robust custom data structure. JSON and XML don’t have built-in ways to enforcing types (like if a numerical value is a decimal or an integer), so that needs to be custom built into the data structure as well if it’s required.

GraphQL is a newer player in the API game. It is a query language that specifies how data should be transmitted between the client and the server. Let’s look at basic RPC, APIs, and GraphQL a bit:

Standard API

POST /Application/Functions/Subtract HTTP/2.0
Content-Type: application/json; charset=utf-8
Connection: close

{"amount": 42, "subtraction": 23}

{"error": false, "result": 19}

Here, we have a very simple API. A client sends a POST request to the /Application/Functions/Subtract path with a JSON object as the POST body. The server returns a simple JSON object as a response.


POST /Application/JSONRPCEndpoint HTTP/2.0
Content-Type: application/json; charset=utf-8
Connection: close

{"jsonrpc": "2.0", "method": "subtract", "params": {"amount": 42, "subtraction": 23}, "id": 3}

{"jsonrpc": "2.0", "result": 19, "id": 3}

RPC Specifications structure data further. Here, using JSON-RPC, the path /Application/JSONRPCEndpoint remains the same, and the method is defined within the structure of the post request. Additionally, the request is given it’s own ID, so it can be referenced. The parameters for the request are explicitly defined in the parameter property. In response, the server returns the result, in addition to the ID of the request to match the response correctly.


POST /Application/GraphQLndpoint HTTP/2.0
Content-Type: application/graphql; charset=utf-8
Connection: close

query subtract(amount: Int!, subtraction: Int): [Int]

  "query": "subtract($amount: Int!, $subtraction: Int): [Int]",
  "operationName": "subtract",
  "variables": { "amount": 42, "subtraction": 23}

  "data": {
    "subtract": 19

Whoa! This looks much more complex. Let’s break it down. First, in most applications, the work of building the POST request that GraphQL makes is done for you. That’s what makes it a query language and not a normal API spec. Here though, I’ve broken it out for comparison to show you what a GraphQL POST request looks like. As you can see, it’s a standard JSON object. Here, we are passing three properties. The query property is where the magic happens. Here, we are building a functional query that uses the subtract operation (which, like the other API examples, would be some function on the server). We are declaring two variables, $amount, and $subtraction. We are enforcing the type of int for both the variables and the result of the query.

We explicitly define the operation name because there can be multiple operations in a query and we are only worried about the subtract one for this query. Since we declared two variables, we need to define them, which occurs in the variables property.

The return object simply gives us the result of the subtract operation.

It’s important to note that all of these languages and specifications purely define the structure of the data being sent between the client in server. API Specifications/structures and query languages do not define how the data is stored. This means that a database server could be MySQL, MSSQL, Oracle, MongoDB, Neo4J, or any other database system, and use JSON, XML, or GraphQL. As an example, SQL is a query language, MySQL is an implementation.

How does Craft fit in?

Craft has taken this trend in full stride. This 3.3 release now includes built-in full support for GraphQL. Before I continue, it’s important to note first, that The GraphQL API is only available on Craft Pro.

Second, the GraphQL API is not enabled out of the box.

Getting started with GraphQL in Craft Pro

Enabling the API

First, you’ll want to add a new route to your config/routes.php file like this:

return [
    'api' => 'graphql/api',
    // ...

You can now test this new route at /api. Just make sure to set the "Content-Type: application/graphql" header.

The easiest way to do this is to send a POST request with {ping} as the body. You should receive {"data":{"ping":"pong"}} as a response.

Defining your schemas

Next, you need to define your schemas. Out of the box, the API will not return any data. It is on your to define what should be considered public, and what should be considered private (you can define multiple private schemas, each with it’s own access token.

Unlike the Element API, GraphQL schemas are defined in the Craft admin section. Navigate to /admin/graphql/schemas on your site, and you can explicitly define which data should be available to which schemas, whether public or private, through a simple GUI interface like this:

GraphQL Schema UI

Another important note here, Craft CMS doesn’t currently support mutations. This means that the GraphQL can be used to consume data, but not do any create, update, or delete style requests. The open source CraftQL plugin does support schemas. I won’t cover it in this article, but you can check it out here:

Querying your data

This is the interesting part. The easiest way to test data queries are with the GraphiQL interface provided by Craft, which can be accessed at /admin/graphql. Here, you can create example queries and variables.

An example query would be:

query ($section:[String], $orderBy:String) {
  entries(section: $section, orderBy: $orderBy) {

And then in the Variables section:

  "section": "products",
  "orderBy": "postDate desc"

Keep in mind that these rules follow Craft’s Element Queries structure, so things like orderBy work the same way as they do in a twig template.

This will get you started, but there is a whole world of queries options available here:

Like all just-release software, the new GraphQL functionality is not without bugs. If you are running into issues, check here to see if someone else has run into the same issue:✓&q=is%3Aissue+is%3Aopen+label%3A"%3Amag%3A+graphql"

What else is there?

With Craft now covering a range of functionality as a headless CMS, you may be suprised to find that there were more features released with 3.3. One of those is Headless Mode. This is a basic config option that does the following:

  • URI Format settings for sections and category groups will be hidden.
  • Template route management will be hidden.
  • Front-end routing will skip checks for element and template requests.
  • Front-end responses will be JSON-formatted rather than HTML by default.
  • Twig will be configured to escape unsafe strings for JavaScript/JSON rather than HTML by default for front-end requests.

You can enable this by setting the headlessMode config option to true in your config.php.

But wait, there’s more!

On top of all these new features, the Craft team has been hard at work improving a few nice-to-haves in the platform:

  • URL-less Singles. It’s now possible to create Single sections that don’t have URLs assigned to them.
  • Stack Traces on Production. Admin users can now opt to see the full stack trace view when exceptions occur on production, without enabling Dev Mode.
  • Better Twig Profiling. Craft now profiles individual template block and macro renders, in addition to full templates, so you can get a better sense of where those performance bottlenecks are.
  • Templating Improvements. There are new tag()input(), and hiddenInput() HTML generation functions, plus |attr|append, and |prepend filters for manipulating existing HTML tags within your templates.

Some of these are huge for developers trying to tweak the most performance out of their twig templates, as well as those using Craft for a more esoteric content management system.

If you’re not a developer (or even if you are!), but want to learn more about how we use Craft CMS here at Digital Surgeons, please Contact Us.