{"record":{"id":"da2e88d0277789b4","repo":"strapi/strapi","slug":"the-attribute-attributename-is-reserved-and-c","errorCode":null,"errorMessage":"The attribute \"${attributeName}\" is reserved and cannot be used in a model. Please rename \"${contentType.modelName}\" attribute \"${attributeName}\" to something else.","messagePattern":"The attribute \"(.+?)\" is reserved and cannot be used in a model\\. Please rename \"(.+?)\" attribute \"(.+?)\" to something else\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/core/src/utils/transform-content-types-to-models.ts","lineNumber":297,"sourceCode":"  contentTypes.forEach((contentType) => {\n    assert(contentType.collectionName, 'Content type \"collectionName\" is required');\n    assert(contentType.modelName, 'Content type \"modelName\" is required');\n    assert(contentType.uid, 'Content type \"uid\" is required');\n\n    // Add document id to content types\n    // as it is not documented\n    const documentIdAttribute: Record<string, Schema.Attribute.AnyAttribute> =\n      contentType.modelType === 'contentType'\n        ? { documentId: { type: 'string', default: createDocumentId } }\n        : {};\n\n    // TODO: this needs to be combined with getReservedNames, we should not be maintaining two lists\n    // Prevent user from creating a documentId attribute\n    const reservedAttributeNames = ['document_id', identifiers.ID_COLUMN];\n    Object.keys(contentType.attributes || {}).forEach((attributeName) => {\n      const snakeCasedAttributeName = _.snakeCase(attributeName);\n      if (reservedAttributeNames.includes(snakeCasedAttributeName)) {\n        throw new Error(\n          `The attribute \"${attributeName}\" is reserved and cannot be used in a model. Please rename \"${contentType.modelName}\" attribute \"${attributeName}\" to something else.`\n        );\n      }\n    });\n\n    if (hasComponentsOrDz(contentType)) {\n      const compoLinkModel = createCompoLinkModel(contentType, identifiers);\n      models.push(compoLinkModel);\n    }\n\n    const model: Model = {\n      uid: contentType.uid,\n      singularName: contentType.modelName,\n      tableName: contentType.collectionName, // This gets shortened in metadata.loadModels(), so we don't shorten here or it will happen twice\n      attributes: {\n        [identifiers.ID_COLUMN]: {\n          type: 'increments',\n        },","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/strapi/strapi/blob/4a4101264d7098754df36e85fa629fd2f2349d8c/packages/core/core/src/utils/transform-content-types-to-models.ts#L279-L315","documentation":"Thrown during content-type-to-model transformation when a user-defined attribute snake-cases to 'document_id' or to the DB id column (identifiers.ID_COLUMN, conventionally 'id'). These names are reserved because Strapi auto-injects a documentId attribute and an auto-increment id column into every content type; allowing a user attribute with the same DB column name would cause a schema collision.","triggerScenarios":"Defining a content type whose schema.json attributes include 'documentId', 'document_id', 'id', 'ID', or any casing that snake-cases to 'document_id' or 'id'. Fires at Strapi boot during metadata loading, before the server starts.","commonSituations":"Migrating from Strapi v4 (where 'id' was the only identifier) to v5 (which adds documentId) and keeping a custom 'id'-like attribute; naming a field 'documentId' for a business concept; importing a content type schema from another system that uses those names.","solutions":["Rename the offending attribute to something that does not snake-case to 'id' or 'document_id' (e.g. 'documentRef', 'externalId', 'customId').","Search the content type's schema.json for keys named id, documentId, document_id and remove or rename them.","After renaming, clear the rebuilt model cache and restart Strapi."],"exampleFix":"// before — src/api/article/content-types/article/schema.json\n{ \"attributes\": { \"documentId\": { \"type\": \"string\" } } }\n// after\n{ \"attributes\": { \"externalDocumentId\": { \"type\": \"string\" } } }","handlingStrategy":"validation","validationCode":"const RESERVED = ['id', 'document_id'];\nconst snake = (s) => s.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();\nconst offenders = Object.keys(schema.attributes).filter((name) => RESERVED.includes(snake(name)));\nif (offenders.length) {\n  throw new Error(`Reserved attribute names: ${offenders.join(', ')}`);\n}","typeGuard":"const isReservedAttributeName = (name: string): boolean => {\n  const snake = name.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();\n  return ['id', 'document_id'].includes(snake);\n};","tryCatchPattern":null,"preventionTips":["Never name a content-type attribute 'id', 'documentId', or any casing that snake-cases to those.","Add a pre-commit lint rule that scans schema.json files for reserved names.","During v4→v5 migration, audit all content types for 'id'-like attributes."],"tags":["content-type","schema","reserved-name","startup","database"],"backgroundTag":null,"analyzedSha":"4a4101264d7098754df36e85fa629fd2f2349d8c","analyzedAt":"2026-08-12T12:47:50.760Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}