Godrej Design Lab

Content Modelling

Content modeling forms the foundation of how content on the website is structured, organized and managed. Let's walk through the essential concepts and practical implementation of content types, attributes, and components.

Creating Content Types

Earlier, we explored what content types are conceptually. Now let's look at how to implement them.

Content types can be created through the following methods:

  • Content-Type Builder: A visual interface that is on the admin dashboard.
  • Strapi CLI: Using the interactive strapi generate command
  • Manually: Create the directories and files by hand

A content type's definition comprises of multiple files.

The files are stored at a specific paths in a Strapi project:

./apps/cms/src/api/[api-name]/content-types/[content-type-name]/schema.json
./apps/cms/src/api/[api-name]/content-types/[content-type-name]/lifecycles.ts
# ^ This file is optional.
./apps/cms/src/api/[api-name]/controllers/[content-type-name].ts
./apps/cms/src/api/[api-name]/routes/[content-type-name].ts
./apps/cms/src/api/[api-name]/services/[content-type-name].ts

While not necessary, it is common convention for [api-name] and [content-type-name] to have the same name.

For example, here are the files for the Color Scheme content type:

./apps/cms/src/api/color-scheme/content-types/color-scheme/schema.json
./apps/cms/src/api/color-scheme/content-types/color-scheme/lifecycles.ts
./apps/cms/src/api/color-scheme/controllers/color-scheme.ts
./apps/cms/src/api/color-scheme/routes/color-scheme.ts
./apps/cms/src/api/color-scheme/services/color-scheme.ts

The schema.json is where the content type's definition lies. The other files are boilerplate in most cases and don't require any customization.

Content Type Schema

Following are definitions of content types from the codebase:

Post

/apps/cms/src/api/post/content-types/post/schema.json
{
"kind": "collectionType",
"collectionName": "posts_v1",
"info": {
"singularName": "post",
"pluralName": "posts",
"displayName": "Posts",
"description": "Create your blog content"
},
"options": {
"draftAndPublish": true
},
"pluginOptions": {
"webtools": {
"enabled": true
}
},
"attributes": {
"page_context": {
"type": "relation",
"relation": "manyToOne",
"target": "api::page-context.page-context",
"required": false
},
"color_scheme": {
"type": "relation",
"relation": "manyToOne",
"target": "api::color-scheme.color-scheme",
"required": false
},
"toc": {
"type": "boolean",
"default": true,
"required": true
},
"title": {
"type": "string",
"required": true
},
"cover": {
"type": "media",
"allowedTypes": [ "images" ],
"multiple": false
},
"category": {
"type": "relation",
"relation": "manyToOne",
"target": "api::post-category.post-category",
"inversedBy": "posts"
},
"header_region": {
"type": "component",
"component": "container.header-region-v1"
},
"main_region": {
"type": "component",
"component": "container.main-region-v1"
},
"side_region": {
"type": "component",
"component": "container.side-region-v1"
},
"url_alias": {
"type": "relation",
"relation": "oneToMany",
"target": "plugin::webtools.url-alias",
"writable": true,
"configurable": false,
"editable": false,
"visible": false,
"unique": true
}
}
}

Color Scheme

/apps/cms/src/api/color-scheme/content-types/color-scheme/schema.json
{
"kind": "collectionType",
"collectionName": "color_schemes_v1",
"info": {
"singularName": "color-scheme",
"pluralName": "color-schemes",
"displayName": "Color Scheme",
"description": "Color pairings to theme the frontend"
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"name": {
"type": "string",
"required": true,
"unique": true
},
"description": {
"type": "text"
},
"primary_color_hex": {
"type": "customField",
"customField": "plugin::color-picker.color",
"required": true,
"regex": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
},
"primary_color_rgb": {
"type": "string",
"required": false
},
"secondary_color_hex": {
"type": "customField",
"customField": "plugin::color-picker.color",
"required": true,
"regex": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
},
"secondary_color_rgb": {
"type": "string",
"required": false
}
}
}

Each content type definition comprises several key sections:

  • Model settings
  • Model information
  • Options
  • Plugin options

Model Settings

  • kind: Defines whether it's a collectionType or singleType
  • collectionName: The database table name where data is stored

Model Information

The info section contains metadata used by the admin panel and APIs:

  • displayName: Human-readable name shown in the admin panel
  • singularName: Singular form used for API routes (kebab-case)
  • pluralName: Plural form used for API routes (kebab-case)
  • description: Description of the content type

Options

The options section controls specific behaviors:

  • draftAndPublish: Enables draft/publish functionality
  • privateAttributes

See Model Options to learn more.

Plugin Options

Custom configurations for specific plugins, as seen in the Post content type with the webtools plugin configuration.


Attributes

Attributes represent the individual fields of content types. Each attribute has a type parameter that informs the type of data/content that it can hold.

A wide range of attribute types are supported:

Scalar Types

  • String types: string, text, richtext, blocks, enumeration, email, password, uid
  • Date types: date, time, datetime, timestamp
  • Number types: integer, biginteger, float, decimal
  • Other types: boolean, JSON

Compound Types

  • media: For files uploaded through the media library
  • relation: to establish a connection/relationship to a documemt/entry
  • component: References a component (covered later)
  • dynamiczone: Defines a content area; a flexible space based on a list of components
  • customField: For custom fields created by plugins

Strapi Components

A component is essentially collection of attributes. They can be re-used across content types and even other components too. They help keep things consistent and reduce duplication.

Creating components

Content types can be created through the following methods:

  • Content-Type Builder: A visual interface that is on the admin dashboard.
  • Manually: Create the component definition file by hand

Unlike content types, a component's definition comprises of just a single file. They are stored at a specific path in a Strapi project:

./apps/cms/src/components/[component-category]/[component-name].json

For example, the defintion file for the Section component is stored at:

./apps/cms/src/components/container/section-v1.json

Each component must be placed within a category subfolder, following this structure:

./apps/cms/src/components/
└── category-name/
├── component-a.json
└── component-b.json

Component examples

Let's look at a few component definitions from the project:

Section component

{
"collectionName": "components_container_section_v1",
"info": {
"displayName": "Section",
"description": "A standalone section of the page."
},
"options": { },
"attributes": {
"register_with_toc": {
"type": "boolean",
"required": true,
"default": false
},
"title": {
"type": "string",
"required": false
},
"heading": {
"type": "component",
"component": "text.heading-v1",
"required": false
},
"collapsible": {
"type": "boolean",
"required": true,
"default": false
},
"collapsed_by_default": {
"type": "boolean",
"required": false
},
"content": {
"type": "dynamiczone",
"components": [
"gdl.heading-and-content-list-v1",
"gdl.image-and-content-v1",
"gdl.image-and-content-list-v1",
"gdl.promo-v1",
"gdl.post-listing-v1",
"gdl.fellowship-v1",
"text.heading-v1",
"text.wysiwyg-v1",
"media.image-v1",
"media.gallery-v1",
"navigation.button-link-v1",
"navigation.image-link-v1"
]
}
}
}

Image link component

{
"collectionName": "components_navigation_image_link_v1",
"info": {
"displayName": "Image Link",
"description": ""
},
"options": { },
"attributes": {
"image": {
"type": "component",
"component": "media.image-v1",
"required": true
},
"link": {
"type": "component",
"component": "navigation.link-v1",
"required": true
}
}
}

Image component

{
"collectionName": "components_media_image_v1",
"info": {
"displayName": "Image",
"description": ""
},
"options": { },
"attributes": {
"file": {
"allowedTypes": [ "images" ],
"type": "media",
"multiple": false,
"required": true
},
"aspect_ratio": {
"type": "enumeration",
"enum": [
"natural",
"1:1 (square)"
],
"required": true,
"default": "natural"
}
}
}

Component Attributes and Nesting

Components follow the same attribute structure as content types, but with an important distinction: component attributes exist under a level of nesting. When you use a component in a content type, the component's attributes are not merged and placed side-by-side with the content type's (or component's) other attributes. Instead, they are nested within the component attribute's key.

For example, in the "Image link" component, image is a component attribute; its internals (file and aspect_ratio) are accessed via <image_link>.image.aspect_ratio, not <image_link>.aspect_ratio (where <image_link> refers to the component's key).

Referencing a Component

Components can be referenced in content types as well as other components using the component attribute type. Consider this excerpt from the Image link component:

"image": {
"type": "component",
"component": "media.image-v1",
"required": true
}

The component parameter follows the format <category>.<componentName>. In the above example, <category> is media and <componentName> is image-v1. This format corresponds to the physical path of the component definition, which in this case, is ./apps/cms/src/components/media/image-v1.json.


Relationship between content types, attributes and components

Both content types and components can contain attributes.
Attributes can refer to data types (scalar or compound) but they can also refer to other components.


Dynamic Zones

Dynamic zones is a type of attribute that can be thought of as buckets that can hold any kind of content. They enable building complex, dynamic layouts by allowing authors to mix and match different components.
Here's an example:

"content": {
"type": "dynamiczone",
"components": [
"gdl.heading-and-content-list-v1",
"gdl.image-and-content-v1",
"text.wysiwyg-v1",
"media.image-v1"
]
}

There are defined using the dynamiczone type and accept a components array.

Only components can be attached to dynamic zones. So if built-in attributes such as enumeration (or media or relation) need to be attached, first you will have to create a component that simply contains a single attribute of type enumeration, and then you can attach that component to the dynamic zone.

The section component in the codebase can contain a variety of different components:

"content": {
"type": "dynamiczone",
"components": [
"gdl.heading-and-content-list-v1",
"gdl.image-and-content-v1",
"gdl.image-and-content-list-v1",
"gdl.promo-v1",
"gdl.post-listing-v1",
"gdl.fellowship-v1",
"text.heading-v1",
"text.wysiwyg-v1",
"media.image-v1",
"media.gallery-v1",
"navigation.button-link-v1",
"navigation.image-link-v1"
]
}

This allows for flexible content composition within each section, allowing authors to build varied layouts using different combinations of headings, text, images, and other content layouts.


Content modelling is at the core of Strapi. Understanding content types, attributes, components and how they relate to each other is key to designing an application's content layer.