# MongoDB vs. SQL implementation differences in Strapi v3

This documentation explains the key structural differences to take into account when migrating data from MongoDB to SQL in the context of a Strapi v3 project. It should be used as a reference when migrating data locally (see MongoDB to SQL migration in Strapi v3).

# Model settings

The model.settings.json files, used to define models in Strapi v3, include parameters that are handled differently by MongoDB and SQL databases.

# Naming conventions

Table/collection names

Table in SQL databases, equivalent to collection in MongoDB, are created with the name defined in the collectionName option of the model.settings.json file.

When switching from MongoDB to SQL, one SQL table is created per MongoDB collection, and new SQL tables are created for relations.

Column/field names

Columns in SQL, equivalent to fields in MongoDB, are created with the names defined in the attributes option of the model.setting.json file.

An example attribute_a defined in model.settings.json would be stored like the following in MongoDB and SQL databases:

// model.settings.json
{
  "attributes": {
    "attribute_a": {
      "type": "string"
    }
  }
}

MongoDB:

{
  "_id": ObjectId("1")
  "attribute_a": "abcd"
}

SQL:

{
  "id": 1
  "attribute_a": "abcd"
}

# Timestamps

If the timestamps option is defined in the model.settings.js file, no migration is required, the properties will be the same in MongoDB and SQL databases.

If no timestamps option is set, the defaults should be migrated, using lower snake case in SQL databases:

Field name in MongoDB Field name in SQL databases
createdAt created_at
updatedAt updated_at

# Relations

✏️ NOTE

Custom column names for relations can't be used in both MongoDB and SQL databases. No specific migrations are needed for this case and custom column names can be considered as if they were not used.

In Strapi, relations between models are defined in the attributes section of the model.settings.json files.

The following section explains how each type of relation is declared in the model attributes and gives an example of how the model attributes are reflected in the MongoDB and SQL databases:

# SQL join table names

The name for the SQL join table used in manyToMany and manyWay relations is generated based on the collectionName property, the attributes of the relation, and the type of the relation:

  • manyToMany relations have the join table follow this naming pattern: {}_{}

  • manyWay relations have the join table follow this naming pattern: {collectionName}__${snakeCase(attributeName)}, like in the following example:

    // With the following model A:
    {
      "collectionName": "table_a",
      "attributes": {
        "myManyWay": {
          // ...
        }
      }
    }
    
    // The SQL join table name will be:
    "table_a__my_many_way"
    

# Components & Dynamic zones

In both MongoDB and SQL databases, components have their own collection and are links to their parent.

In MongoDB, the links are done via an array of objects stored in the parent. Even non-repeatable components are listed in an array. Each object from this array has 2 properties:

  • ref targets a specific component
  • kind targets a specific collection

In SQL databases, the links are done with a SQL join table. The table name is generated following this pattern: {collectionName}_components, where collectionName is in the parent model. SQL tables for components include the following elements:

Name Type Description
component_type Column Uses the collectionName and not the globalId property
field Column Should be equal to the attribute name
order Column Should go from 1 to x, matching the order in the MongoDB array
component_id Foreign key Targets the component table
{singular(collectionName)}_id Foreign key Targets the parent table
Example of a component definition in model settings, MongoDB and SQL databases in Strapi v3

Models:

// model A
{
  "attributes": {
    "compo": {
      "type": "component"
      "repeatable": true|false
    }
  }
}

// Component
{
  "attributes": {}
}

Mongo:

// model A
{
  "_id": ObjectId("1"),
  "compo": [
    {
      "_id": ObjectId("xxx"), // this id doesn't matter
      "kind": "CompoGlobalId", // to be converted to collectionName before creating the join in SQL
      "ref": ObjectId("1") // actual id of the component
    }
  ]
}

// Component
{
  "_id": ObjectId("1"),
}

SQL:

// model A
{
  "id": 1,
}

// Component
{
  "id": 1,
}

// A_components
{
  "id": 1,
  "field": "compo",
  "order": 1,
  "component_type": "compos",
  "component_id": 1,
  "a_id": 1
}

# Media

Media are stored the same way in MongoDB and in SQL. However, the links created between media and entries are stored differently:

In MongoDB, media links are stored on both sides of the relation. The related property is an array of objects targeting the related entries in the media collection, called upload_file. Each object has 3 properties:

  • ref targets a specific media
  • kind targets a specific collection
  • field targets a specific attribute

MongoDB also includes a property in the entries, named like the media attributes of the models, which is either an array or a single ObjectId targetting the media(s).

In SQL databases, an upload_file_morph join table is created, with the following elements:

Name Type Description
upload_file_id Foreign key Targets the media
related_id Column Targets the entry
related_type
Example of media definition in model settings, MongoDB, and SQL databases in Strapi v3

Models:

// model A
{
  "attributes": {
    "pictures": {
      "plugin": "upload",
      "collection": "file", // multiple files
      "via": "related",
      
    }
  }
}

// model B
{
  "attributes": {
    "cover": {
      "plugin": "upload",
      "model": "file", // single file
      "via": "related",
    }
  }
}

MongoDB:

// model A
{
  "_id": ObjectId("1"),
  "pictures": [
    ObjectId("1"),
  ]
}

// model B
{
  "_id": ObjectId("1"),
  "cover": ObjectId("1")
}

// upload_file
{
  "_id": ObjectId("1"),
  // ...
  "related": [
    {
      "_id": ObjectId("1"), // this id doesn't matter
      "kind": "GlobalIdOfA", // needs to be converted to collectionName for SQL
      "ref": ObjectId("1"), // id of the A entry
      "field": "pictures", // field in A to which the media is linked
    },
    {
      "_id": ObjectId("2"), // this id doesn't matter
      "kind": "GlobalIdOfB", // needs to be converted to collectionName for SQL
      "ref": ObjectId("1"), // id of the B entry
      "field": "cover", // field in B to which the media is linked
    }
  ]
}

SQL:

// model A
{
  "id": 1,
}

// model B
{
  "_id": 1,
}

// upload_file
{
  "id": 1,
}

// upload_file_morph
[
  {
    "id": 1, // this id doesn't matter
    "upload
    "related_type": "collectionNameofA", // collectionName of A
    "related_id": 1, // id of the A entry
    "field": "pictures", // field in A to which the media is linked
    "order": 1,
  },
  {
    "id": 2, // this id doesn't matter
    "related_type": "collectionNameofB", // needs to be converted to collectionName for SQL
    "related_id": 1, // id of the B entry
    "field": "cover", // field in B to which the media is linked
    "order": 1
  }
]

# Scalar attributes

There are no structural changes in the scalar attributes between MongoDB and SQL databases.

The only differences to take into account are the following:

  • time stores milliseconds.
  • json is an object in MongoDB. Make sure to stringify it if necessary in the SQL database you target (SQLite or MySQL < 5.6).

# Attributes created by Strapi

With the exception of timestamps, attributes created by Strapi are the same in Mongo and SQL databases. This includes the following attributes:

  • published_at
  • created_by
  • updated_by
  • locale

localizations is a manyWay relation (see relations).

# Custom use cases

The following table highlights some specific uses cases and their possible resolution:

Use case Resolution
Custom id types Custom ID types are only used in SQL. No migration is required since the feature is not supported in MongoDB.
Custom indexing Custom indexing is not a supported feature. Equivalent indexes must be created in SQL manually.
Custom join table names Custom join table names should be taken into account when migrating the relations to find the right table name (see SQL join table names).
Custom DB queries Migrate to v3 SQL then to Strapi v4, and finally migrate the custom queries with the Query Engine of Strapi v4.