Definition reference

Learn how to define your content using content definitions.

When you create content, you are essentially filling in a template defined by a schema with the actual values you want to display in your application.

This reference guide outlines the options available for defining your content and how to use them.

Croct's mascot neutral
Do I need to read this?

Through the platform, you'll only interact with forms, not content definitions. But if you want to define your content using JSON, this guide is for you.

Key concepts

This section introduces some essential terms and concepts related to content definitions. Familiarizing yourself with them will help you fully understand the rest of this guide.

Content definition

A content definition is a configuration written in JSON that defines the actual values for the attributes described in your content schema. It specifies the data that your content will have and its types. You can think of a content definition as a filled-out form based on your schema.

Primitive values

Primitive values are simple, single values like text, boolean, or number. What distinguishes them from other types is that they can be static or dynamic, as explained below.

Static values

Static values are the ones we are all familiar with. They are simple, constant values, like the text "Hello world" or the number 42. For most CMS platforms, static values are the only type of value you can use.

Dynamic values

As a personalization platform, we offer more than just static values. You can personalize your content in real time and even use CQL expressions to generate values dynamically based on the user's context.

Dynamic values are only supported for primitive types and always require a fallback value. Because it is evaluated in real time, having a fallback value protects your application from runtime errors, giving you a fail-safe mechanism. This means that if the dynamic value fails to evaluate or the result does not match the expected type, the fallback value is used instead.

Placeholders

Placeholders are special markers you can use in text attributes to insert content from other attributes.

You can reference attributes either relatively (from the location of the current attribute) or absolutely (from the root of the content). You can also provide a default value to use in case the referenced attribute does not exist or is not a primitive value.

The basic syntax for a placeholder is {{attribute}}, where attribute is the path to the attribute you want to insert. You can optionally provide a fallback value by adding a pipe character |, like in {{attribute | fallback value}}.

The attribute paths can use two types of notation:

  • Name notation
    For referencing an attribute by its name, like title.
  • Index notation
    For referencing a list item by its index, like buttons[0].

You can combine both notations to reference nested attributes. For example, buttons[1].label.

Absolute paths

Absolute paths start with a property name or index and use the root of the content as a starting point for resolving the path, regardless of the location of the attribute containing the placeholder. This is useful for referencing global values in your content, like shared properties and metadata.

As with relative paths, you can use both name and index notations to reference nested attributes, like offer.title and features[0].

Let's illustrate this with an example:

{
"offer": {
"title": "Try free for {{offer.duration}} days and {{features[0]}}",
"duration": 30
},
"features": [
"unlimited projects",
"unlimited collaborators",
"unlimited storage"
],
"header": "unlimited collaborators"
}

Relative paths

Relative paths start with double dots (..) and use the location of the attribute containing the placeholder as a starting point for resolving the path.

For example, {{..price}} references a sibling attribute price, while {{..[0]}} references the first item of the parent list.

You can also refer to the nth-level parent attribute with .. followed by the level, like {{..2.price}}. The index is zero-based, starting from the current attribute, where ..0 refers to the current attribute, ..1 refers to the parent attribute, and so on. Note that omitting the level number assumes the parent attribute, so {{..sibling}} and {{..1.sibling}} are equivalent.

Using our previous example, we can also use relative paths to achieve the same result:

{
"offer": {
"title": "Try free for {{..duration}} days and {{..2.features[0]}}",
"duration": 30
},
"features": [
"unlimited projects",
"unlimited collaborators",
"unlimited storage"
]
}

The placeholder {{..duration}} references the sibling attribute duration, while the placeholder {{..2.features[0]}} references the first item in the features list, which is two levels above the title attribute.

So, the offer title would be "Try free for 30 days and unlimited projects".

Fallback values

Lastly, you can optionally provide a fallback value for cases where the reference points to a missing or non-primitive value. When not specified, it defaults to an empty string.

For example, you can use the placeholder {{description | No description}} to fall back to "No description" (or any other text) in the absence of the description attribute, assuming it is optional. Leading and trailing whitespace are trimmed from the default value.

To use a literal character in your default value, you can escape it with a backslash \. For example, you can use {{description | \ No description}} to force a space before the default value.

It is important to note that placeholder behavior depends on the value type of the referenced attribute:

  • Text attributes: the reference is inserted into the final value as-is.
  • Numeric attributes: the number is formatted and converted to text, following the same behavior as in JavaScript.
  • Boolean attributes: the reference is rendered as either true or false.
  • Other values (including null): the reference is silently ignored and becomes an empty string in the final value.

Cheat sheet

Here is a quick cheat sheet with examples of placeholders that you can copy and paste into your content definitions:

PlaceholderDescription
{{attribute}}Insert a root attribute called attribute.
{{attribute.child}}Insert a nested attribute called child from a root attribute called attribute.
{{attribute | fallback}}Insert a root attribute called attribute, or a fallback value if it does not exist.
{{attribute[n]}}Insert the nth item of a list attribute called attribute at the root level.
{{..attribute}}Insert a sibling attribute called attribute.
{{..attribute.child}}Insert a nested attribute called child from a sibling attribute called attribute.
{{..[n]}}Insert the nth item of the parent list.
{{..[n].attribute}}Insert an attribute called attribute from the nth item of the parent list.
{{..n.attribute}}Insert a parent attribute called attribute that is n levels above the current attribute.
{{attribute | \ fallback}}Escape a leading space in the default value.

Content types

Now that we have covered the key concepts around content, let's look at its definition.

When it comes to personalizable content, it is important to understand that its definition is different from the format you see on the front end or other endpoints. Unlike static content, personalizable content requires additional information about the expected type, fallback value, etc.

Below you can see the difference between a final output and its definition:

{
"heading": "Try free for 30 days",
"subheading": "No credit card required."
}

With that in mind, let's look at the different types of content and how they are defined.

Primitive types

Primitive types are the most straightforward yet most powerful content types. Simple because they represent basic values like strings, numbers, and booleans. Powerful because they support dynamic values.

Each primitive content type has a corresponding schema type with the same name. For example, a text content type specifies a string value that must conform to its associated text schema type.

In terms of definition, all primitive content types share the same format:

{
"type": "text" | "number" | "boolean",
"value": {
"type": "static",
"value": string | number | boolean
}
}

For both static and dynamic values, the type of the value, default, and the result of the expression must match the type specified at the root level.

Static properties

The following properties are available for static primitives:

typestring

The type of the value. Always static for static primitives.

valueprimitive

The value of the primitive, which must match the specified type at the root level.

Dynamic properties

The following properties are available for dynamic primitives:

typestring

The type of the value. Always dynamic for dynamic primitives.

expressionstring

The CQL expression that evaluates to the value of the primitive.

nullableboolean

Whether the primitive can be null. Must correspond to the optional property of the associated attribute.

Default:false
defaultprimitive

The default value to use when the evaluation fails, results in null, or produces an incompatible type. The result must match the type specified at the root level.

Examples

Here are some examples of content definitions for primitive types:

{
"type": "text",
"value": {
"type": "static",
"value": "Sign up for free"
}
}

It is worth revisiting the example definition in the introduction, as it demonstrates a common pattern for dynamic values: return null when the condition is not met, so the default is used instead. Otherwise, we would need to specify the default value in the expression as well:

{
"type": "text",
"value": {
"type": "dynamic",
"expression": "'No credit card required.' if location's country is 'us' else 'It only takes a few seconds.'",
"default": "It only takes a few seconds."
}
}

Note that this approach does not work for nullable attributes since null is a valid value in this case. Consequently, the default value would never be applied.

Structure

A structure is a collection of key-value pairs called attributes, where the keys are strings, and the values can be any content type. It is equivalent to objects, associative arrays, or dictionaries in other languages.

Properties

The following properties are available for structures:

typestring

The type of the value. Always structure for structures.

name(optional)string

The name of the structure. It must only be specified if the structure is part of a union. The name must be the key of the corresponding type in the union schema.

attributesobject

The attributes of the structure, where the keys are the attribute names and the values are the corresponding content definitions.

Examples

Here are some examples of content definitions for structures:

{
"type": "structure",
"attributes": {
"heading": {
"type": "text",
"value": {
"type": "static",
"value": "Sign up for free"
}
},
"subheading": {
"type": "text",
"value": {
"type": "dynamic",
"expression": "'No credit card required.' if location's country is 'us' else null",
"default": "It only takes a few seconds."
}
}
}
}

List

A list is an ordered collection of values, where each value can be any content type. It is equivalent to arrays or lists in other languages.

Properties

The following properties are available for lists:

typestring

The type of the value. Always list for lists.

itemsobject

The items of the list, where each item is a content definition.

Examples

Here are some examples of content definitions for lists:

{
"type": "list",
"items": [
{
"type": "text",
"value": {
"type": "static",
"value": "Unlimited projects"
}
},
{
"type": "text",
"value": {
"type": "static",
"value": "Unlimited users"
}
}
]
}

Complexity

Content complexity and schema complexity are two closely-related concepts. Schema complexity refers to the estimated complexity of the content, while content complexity refers to the actual complexity of the content.

That is because structures have optional attributes, lists have varying numbers of elements, and unions can contain types of different complexity. The schema alone cannot capture all of these factors.

To calculate the score, start with 0 and then add up the complexity of the component's attributes as follows:

  • Structures: Count 1 for the structure itself, and add the complexity of its attributes.
  • Lists: Count 1 for the list itself, plus the complexity of each item in the list.
  • Primitives: Count 1 for each primitive type.

Let's apply this to the following example:

{
"type": "structure",
"attributes": {
"heading": {
"type": "text",
"value": {
"type": "static",
"value": "Sign up for free"
}
},
"subheading": {
"type": "text",
"value": {
"type": "static",
"value": "It only takes a few seconds."
}
},
"features": {
"type": "list",
"items": [
{
"type": "text",
"value": {
"type": "static",
"value": "Unlimited projects"
}
},
{
"type": "text",
"value": {
"type": "static",
"value": "Unlimited users"
}
}
]
}
}
}

Based on the rules above, we can calculate the complexity as follows:

  • Count 1 for the structure itself
  • Count 3 for the attributes
  • Count 2 for the items in the list.

That gives us a complexity score of 6.

Limits

To ensure optimal performance, we enforce some limits on the content in addition to those on the schema:

  • Up to 20 items in a list
  • Up to 10 dynamic attributes in the whole content
  • Maximum complexity score of 50.

For tips on designing components that are both flexible and maintainable, check out the Component design guide.

Further reading

For more on content and related topics, check out the following resources: