Skip to main content

Models

As Strapi is a headless Content Management System (CMS), creating a data structure for the content is one of the most important aspects of using the software. Models define a representation of the data structure.

There are 2 different types of models in Strapi:

  • content-types, which can be collection types or single types, depending on how many entries they manage,
  • and components that are data structures re-usable in multiple content-types.

If you are just starting out, it is convenient to generate some models with the Content-type Builder directly in the admin panel. The user interface takes over a lot of validation tasks and showcases all the options available to create the content's data structure. The generated model mappings can then be reviewed at the code level using this documentation.

Model creation​

Content-types and components models are created and stored differently.

Content-types​

Content-types in Strapi can be created:

The content-types use the following files:

  • schema.json for the model's schema definition. (generated automatically, when creating content-type with either method)
  • lifecycles.js for lifecycle hooks. This file must be created manually.

These models files are stored in ./src/api/[api-name]/content-types/[content-type-name]/, and any JavaScript or JSON file found in these folders will be loaded as a content-type's model (see project structure).

note

In TypeScript-enabled projects, schema typings can be generated using the ts:generate-types command (e.g., npm run strapi ts:generate-types or yarn strapi ts:generate-types).

Components​

Component models can't be created with CLI tools. Use the Content-type Builder or create them manually.

Components models are stored in the ./src/components folder. Every component has to be inside a subfolder, named after the category the component belongs to (see project structure).

Model schema​

The schema.json file of a model consists of:

  • settings, such as the kind of content-type the model represents or the table name in which the data should be stored,
  • information, mostly used to display the model in the admin panel and access it through the REST and GraphQL APIs,
  • attributes, which describe the data structure of the model,
  • and options used to defined specific behaviors on the model.

Model settings​

General settings for the model can be configured with the following parameters:

ParameterTypeDescription
collectionNameStringDatabase table name in which the data should be stored
kindOptional,only for content-typesStringDefines if the content-type is:
  • a collection type (collectionType)
  • or a single type (singleType)
// ./src/api/[api-name]/content-types/restaurant/schema.json

{
"kind": "collectionType",
"collectionName": "Restaurants_v1",
}

Model information​

The info key in the model's schema describes information used to display the model in the admin panel and access it through the Content API. It includes the following parameters:

ParameterTypeDescription
displayNameStringDefault name to use in the admin panel
singularNameStringSingular form of the content-type name.Used to generate the API routes and databases/tables collection.Should be kebab-case.
pluralNameStringPlural form of the content-type name.Used to generate the API routes and databases/tables collection.Should be kebab-case.
descriptionStringDescription of the model
./src/api/[api-name]/content-types/restaurant/schema.json

"info": {
"displayName": "Restaurant",
"singularName": "restaurant",
"pluralName": "restaurants",
"description": ""
},

Model attributes​

The data structure of a model consists of a list of attributes. Each attribute has a type parameter, which describes its nature and defines the attribute as a simple piece of data or a more complex structure used by Strapi.

Many types of attributes are available:

  • scalar types (e.g. strings, dates, numbers, booleans, etc.),
  • Strapi-specific types, such as:

The type parameter of an attribute should be one of the following values:

Type categoriesAvailable types
String types
  • string
  • text
  • richtext
  • enumeration
  • email
  • password
  • uid
Date types
  • date
  • time
  • datetime
  • timestamp
Number types
  • integer
  • biginteger
  • float
  • decimal
Other generic types
  • boolean
  • json
Special types unique to Strapi
Internationalization (i18n)-related types_Can only be used if the i18n plugin is installed_
  • locale
  • localizations
caution

Never name a custom attribute locale because it could interfere with, and break, the i18n feature.

Validations​

Basic validations can be applied to attributes using the following parameters:

ParameterTypeDescriptionDefault
requiredBooleanIf true, adds a required validator for this propertyfalse
maxIntegerChecks if the value is greater than or equal to the given maximum-
minIntegerChecks if the value is less than or equal to the given minimum-
minLengthIntegerMinimum number of characters for a field input value-
maxLengthIntegerMaximum number of characters for a field input value-
privateBooleanIf true, the attribute will be removed from the server response.πŸ’‘ This is useful to hide sensitive data.false
configurableBooleanIf false, the attribute isn't configurable from the Content-type Builder plugin.true
./src/api/[api-name]/content-types/restaurant/schema.json

{
// ...
"attributes": {
"title": {
"type": "string",
"minLength": 3,
"maxLength": 99,
"unique": true
},
"description": {
"default": "My description",
"type": "text",
"required": true
},
"slug": {
"type": "uid",
"targetField": "title"
}
// ...
}
}

Database validations and settings​

🚧 This API is considered experimental.

These settings should be reserved to an advanced usage, as they might break some features. There are no plans to make these settings stable.

Database validations and settings are custom options passed directly onto the tableBuilder Knex.js function during schema migrations. Database validations allow for an advanced degree of control for setting custom column settings. The following options are set in a column: {} object per attribute:

ParameterTypeDescriptionDefault
namestringChanges the name of the column in the database-
defaultTostringSets the database defaultTo, typically used with notNullable-
notNullablebooleanSets the database notNullable, ensures that columns cannot be nullfalse
unsignedbooleanOnly applies to number columns, removes the ability to go negative but doubles maximum lengthfalse
uniquebooleanEnforces database level unique, caution when using with draft & publish featurefalse
typestringChanges the database type, if type has arguments, you should pass them in args-
argsarrayArguments passed into the Knex.js function that changes things like type[]
./src/api/[api-name]/content-types/restaurant/schema.json

{
// ...
"attributes": {
"title": {
"type": "string",
"minLength": 3,
"maxLength": 99,
"unique": true,
"column": {
"unique": true // enforce database unique also
}
},
"description": {
"default": "My description",
"type": "text",
"required": true,
"column": {
"defaultTo": "My description", // set database level default
"notNullable": true // enforce required at database level, even for drafts
}
},
"rating": {
"type": "decimal",
"default": 0,
"column": {
"defaultTo": 0,
"type": "decimal", // using the native decimal type but allowing for custom precision
"args": [
6,1 // using custom precision and scale
]
}
}
// ...
}
}

uid type​

The uid type is used to automatically prefill the field value in the admin panel with a unique identifier (UID) (e.g. slugs for articles) based on 2 optional parameters:

  • targetField (string): If used, the value of the field defined as a target is used to auto-generate the UID.
  • options (string): If used, the UID is generated based on a set of options passed to the underlying uid generator. The resulting uid must match the following regular expression pattern: /^[A-Za-z0-9-_.~]*$.

Relations​

Relations link content-types together. Relations are explicitly defined in the attributes of a model with type: 'relation' and accept the following additional parameters:

ParameterDescription
relationThe type of relation among these values:
  • oneToOne
  • oneToMany
  • manyToOne
  • manyToMany
targetAccepts a string value as the name of the target content-type
mappedBy and inversedByOptionalIn bidirectional relations, the owning side declares the inversedBy key while the inversed side declares the mappedBy key

one-to-one" label="One-to-one">

One-to-One relationships are useful when one entry can be linked to only one other entry.

They can be unidirectional or bidirectional. In unidirectional relationships, only one of the models can be queried with its linked item.

Unidirectional use case example:

  • A blog article belongs to a category.
  • Querying an article can retrieve its category,
  • but querying a category won't retrieve the owned article.
./src/api/[api-name]/content-types/article/schema.json

// …
attributes: {
category: {
type: 'relation',
relation: 'oneToOne',
target: 'category',
},
},
// …

Bidirectional use case example:

  • A blog article belongs to a category.
  • Querying an article can retrieve its category,
  • and querying a category also retrieves its owned article.
./src/api/[api-name]/content-types/article/schema.json

// …
attributes: {
category: {
type: 'relation',
relation: 'oneToOne',
target: 'category',
inversedBy: 'article',
},
},
// …

./src/api/[api-name]/content-types/category/schema.json

// …
attributes: {
article: {
type: 'relation',
relation: 'oneToOne',
target: 'article',
mappedBy: 'category',
},
},
// …

one-to-many" label="One-to-Many">

One-to-Many relationships are useful when:

  • an entry from a content-type A is linked to many entries of another content-type B,
  • while an entry from content-type B is linked to only one entry of content-type A.

One-to-many relationships are always bidirectional, and are usually defined with the corresponding Many-to-One relationship:

Example: A person can own many plants, but a plant is owned by only one person.

./src/api/[api-name]/content-types/plant/schema.json

// …
attributes: {
owner: {
type: 'relation',
relation: 'manyToOne',
target: 'api::person.person',
inversedBy: 'plants',
},
},
// …

./src/api/person/models/schema.json

// …
attributes: {
plants: {
type: 'relation',
relation: 'oneToMany',
target: 'api::plant.plant',
mappedBy: 'owner',
},
},
// …

many-to-one" label="Many-to-One">

Many-to-One relationships are useful to link many entries to one entry.

They can be unidirectional or bidirectional. In unidirectional relationships, only one of the models can be queried with its linked item.

Unidirectional use case example:

A book can be written by many authors.

./src/api/[api-name]/content-types/book/schema.json

// …
attributes: {
author: {
type: 'relation',
relation: 'manyToOne',
target: 'author',
},
},
// …

Bidirectional use case example:

An article belongs to only one category but a category has many articles.

./src/api/[api-name]/content-types/article/schema.json

// …
attributes: {
author: {
type: 'relation',
relation: 'manyToOne',
target: 'category',
inversedBy: 'article',
},
},
// …
./src/api/[api-name]/content-types/category/schema.json

// …
attributes: {
books: {
type: 'relation',
relation: 'oneToMany',
target: 'article',
mappedBy: 'category',
},
},
// …

many-to-many" label="Many-to-Many">

Many-to-Many relationships are useful when:

  • an entry from content-type A is linked to many entries of content-type B,
  • and an entry from content-type B is also linked to many entries from content-type A.

Many-to-many relationships can be unidirectional or bidirectional. In unidirectional relationships, only one of the models can be queried with its linked item.

Unidirectional use case example:

  // …
attributes: {
categories: {
type: 'relation',
relation: 'manyToMany',
target: 'category',
},
},
// …

Bidirectional use case example:

An article can have many tags and a tag can be assigned to many articles.

/src/api/[api-name]/content-types/article/schema.json

// …
attributes: {
tags: {
type: 'relation',
relation: 'manyToMany',
target: 'tag',
inversedBy: 'articles',
},
},
// …
./src/api/[api-name]/content-types/tag/schema.json

// …
attributes: {
articles: {
type: 'relation',
relation: 'manyToMany',
target: 'article',
mappedBy: 'tag',
},
},
// …

Custom fields​

Custom fields extend Strapi’s capabilities by adding new types of fields to content-types. Custom fields are explicitly defined in the attributes of a model with type: customField. Custom fields' attributes also accept:

Custom fields' attributes also show the following specificities:

  • a customField attribute whose value acts as a unique identifier to indicate which registered custom field should be used. Its value follows:
    • either the plugin::plugin-name.field-name format if a plugin created the custom field
    • or the global::field-name format for a custom field specific to the current Strapi application
  • and additional parameters depending on what has been defined when registering the custom field (see custom fields documentation).
./src/api/[apiName]/[content-type-name]/content-types/schema.json

{
// …
"attributes": {
"attributeName": { // attributeName would be replaced by the actual attribute name
"type": "customField",
"customField": "plugin::color-picker.color",
"options": {
"format": "hex"
}
}
}
// …
}

Components​

Component fields create a relation between a content-type and a component structure. Components are explicitly defined in the attributes of a model with type: 'component' and accept the following additional parameters:

ParameterTypeDescription
repeatableBooleanCould be true or false depending on whether the component is repeatable or not
componentStringDefine the corresponding component, following this format:<category>.<componentName>
./src/api/[apiName]/restaurant/content-types/schema.json

{
"attributes": {
"openinghours": {
"type": "component",
"repeatable": true,
"component": "restaurant.openinghours"
}
}
}

Dynamic zones​

Dynamic zones create a flexible space in which to compose content, based on a mixed list of components.

Dynamic zones are explicitly defined in the attributes of a model with type: 'dynamiczone'. They also accept a components array, where each component should be named following this format: <category>.<componentName>.

./src/api/[api-name]/content-types/article/schema.json

{
"attributes": {
"body": {
"type": "dynamiczone",
"components": ["article.slider", "article.content"]
}
}
}

Model options​

The options key is used to define specific behaviors and accepts the following parameter:

ParameterTypeDescription
privateAttributesArray of stringsAllows treating a set of attributes as private, even if they're not actually defined as attributes in the model. It could be used to remove them from API responses timestamps. The privateAttributes defined in the model are merged with the privateAttributes defined in the global Strapi configuration.
draftAndPublishBooleanEnables the draft and publish feature. Default value: true (false if the content-type is created from the interactive CLI).
./src/api/[api-name]/content-types/restaurant/schema.json

{
"options": {
"privateAttributes": ["id", "createdAt"],
"draftAndPublish": true
}
}

Lifecycle hooks​

Lifecycle hooks are functions that get triggered when Strapi queries are called. They are triggered automatically when managing content through the administration panel or when developing custom code using queriesΒ·

Lifecycle hooks can be customized declaratively or programmatically.

caution

Lifecycles hooks are not triggered when using directly the knex library instead of Strapi functions.

Please refer to the error handling documentation to learn how to throw errors from lifecycle hooks. :::

Available lifecycle events​

The following lifecycle events are available:

  • beforeCreate
  • beforeCreateMany
  • afterCreate
  • afterCreateMany
  • beforeUpdate
  • beforeUpdateMany
  • afterUpdate
  • afterUpdateMany
  • beforeDelete
  • beforeDeleteMany
  • afterDelete
  • afterDeleteMany
  • beforeCount
  • afterCount
  • beforeFindOne
  • afterFindOne
  • beforeFindMany
  • afterFindMany

Hook event object​

Lifecycle hooks are functions that take an event parameter, an object with the following keys:

KeyTypeDescription
actionStringLifecycle event that has been triggered (see list)
modelArray of strings (uid)An array of uids of the content-types whose events will be listened to.If this argument is not supplied, events are listened on all content-types.
paramsObjectAccepts the following parameters:
  • data
  • select
  • where
  • orderBy
  • limit
  • offset
  • populate
resultObject_Optional, only available with afterXXX events_Contains the result of the action.
stateObjectQuery state, can be used to share state between beforeXXX and afterXXX events of a query.

Declarative and programmatic usage​

To configure a content-type lifecycle hook, create a lifecycles.js file in the ./src/api/[api-name]/content-types/[content-type-name]/ folder.

Each event listener is called sequentially. They can be synchronous or asynchronous.

Declarative usage​

./src/api/[api-name]/content-types/[content-type-name]/lifecycles.js

module.exports = {
beforeCreate(event) {
const { data, where, select, populate } = event.params;

// let's do a 20% discount everytime
event.params.data.price = event.params.data.price * 0.8;
},

afterCreate(event) {
const { result, params } = event;

// do something to the result;
},
};
./src/api/[api-name]/content-types/[content-type-name]/lifecycles.ts

export default {
beforeCreate(event) {
const { data, where, select, populate } = event.params;

// let's do a 20% discount everytime
event.params.data.price = event.params.data.price * 0.8;
},

afterCreate(event) {
const { result, params } = event;

// do something to the result;
},
};

Programmatic usage​

Using the database layer API, it's also possible to register a subscriber and listen to events programmatically:

./src/index.js
module.exports = {
async bootstrap({ strapi }) {
// registering a subscriber
strapi.db.lifecycles.subscribe({
models: [], // optional;

beforeCreate(event) {
const { data, where, select, populate } = event.params;

event.state = 'doStuffAfterWards';
},

afterCreate(event) {
if (event.state === 'doStuffAfterWards') {
}

const { result, params } = event;

// do something to the result
},
});

// generic subscribe for generic handling
strapi.db.lifecycles.subscribe((event) {
if (event.action === 'beforeCreate') {
// do something
}
});
}
}