JSON Fields New!

Validate and store arbitrary JSON data in a simple CodeMirror (opens new window) editor. The data is automatically deserialized for you and can be used in templates as whatever value type was stored.

Screenshot of a JSON field interface in the Craft control panel

#Settings

my-craft-project.ddev.site/admin/settings/fields/new
JSON field settings screen in the Craft control panel
Adding a new JSON field via the control panel.

JSON fields have no custom settings.

#Development

The JSON field returns an instance of craft\fields\data\JsonData (opens new window), which has a handful of helpful features for interacting with the deserialized data.

Directly outputting the field’s value serializes it back to a JSON string:

{{ entry.myJsonField }}

To pretty-print the JSON, you must explicitly call the .getJson() method:

<pre>{{ entry.myJsonField.getJson(true) }}</pre>

<pre> tags are necessary to retain formatting, in the browser. Similarly, the <code> tag can be used for inline values.

The second argument allows you to customize the indentation:

{# Use tabs instead of the default (two spaces): #}
<pre>{{ entry.myJsonField.getJson(true, '\t') }}</pre>

#Data Types

The data type of the JSON’s root element can be a boolean, integer, float, string, or array.

<div class="json {{ data.getType() }}">{{ data }}</div>

The scalar test makes it possible to quickly differentiate simple values from arrays and hashes:

{% if data.getValue() is scalar %}
  {# Output directly: #}
  {{ data }}
{% else %}
  {# This might be more complex, so we’ll print it as nicely as possible: #}
  <pre>{{ data.getJson(true) }}</pre>
{% endif %}

Note that we’re directly testing against the field’s underlying deserialized value, not the JsonData object that wraps it. The latter will always be non-scalar!

If you plan to use the root JSON field value as a specific type, you may want to take additional steps to resolve a usable value. Take this example, where we’re directly configuring a JavaScript-based animation, but constrain simple “duration” values:

{% set config = section.animationConfig %}

{# Constrain numeric, “duration-only” values: #}
{% if section.animationConfig.getType() in ['integer', 'float'] %}
  {% set config = max(min(section.animationConfig, 1000), 100) %}
{% endif %}

{# Output the resolved value in a script: #}
<script>
  new FadeAnimation("animated-{{ section.id }}", {{ config }});
</script>

#Nested Keys

If you know the structure of a JSON value, you can access nested keys directly from the field:

{{ entry.myJsonField.appId }}

The field ensures values are valid JSON, but does not enforce any kind of schema. You should always handle input cautiously, to avoid runtime errors:

{{ entry.myJsonField.appId ?? null }}

#Objects + Arrays

JSON “objects” and “arrays” are deserialized into PHP arrays (associative and sequential, respectively), and the root JsonData object provides a means of iterating over them, directly:

<table>
  <thead>
    <tr>
      <th>Key</th>
      <th>Value</th>
    </tr>
  </thead>
  <tbody>
    {% for key, val in entry.myJsonField %}
        <tr>
          <td>{{ key }}</td>
          <td>
            {# Make sure the value is printable: #}
            <pre>{{ val is scalar ? val : val|json_encode }}</pre>
          </td>
        </tr>
    {% endfor %}
  </tbody>
</table>

Once you have traversed into a data structure, you must explicitly serialize nested objects for output—Craft doesn’t recursively wrap that data with instances of JsonData, which provides the magic __toString() method. This also means that .getType() and other helper methods are not available on nested values.

Craft provides a number of Twig tests for checking value types.

#GraphQL

JSON fields are treated like plain strings, when queried via GraphQL. Values must be deserialized in the client or app:

const gql = `
query MyQuery {
  globalSet(handle: "siteOptions") {
    ... on siteOptions_GlobalSet {
      analyticsConfig
    }
  }
}
`;

fetch('/api', {
  body: gql,
  method: 'POST',
  headers: {
    'Content-Type': 'application/graphql',
    'X-Craft-Gql-Schema': '*',
  },
})
  // Unpack the text response:
  .then(response => response.json())
  // Deserialize the JSON field’s data:
  .then(json => JSON.parse(json.data.globalSet.analyticsConfig))
  // Use the returned object:
  .then(config => new AcmeAnalytics(config));