{"id":"db7b964202732aea","repo":"sequelize/sequelize","slug":"the-through-option-is-not-available-in-hasmany","errorCode":null,"errorMessage":"The \"through\" option is not available in hasMany. N:M associations are defined using belongsToMany instead.","messagePattern":"The \"through\" option is not available in hasMany\\. N:M associations are defined using belongsToMany instead\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/associations/has-many.ts","lineNumber":128,"sourceCode":"    target: ModelStatic<T>,\n    options: NormalizedHasManyOptions<SourceKey, TargetKey>,\n    parent?: Association,\n    inverse?: BelongsToAssociation<T, S, TargetKey, SourceKey>,\n  ) {\n    if (options.sourceKey && !source.getAttributes()[options.sourceKey]) {\n      throw new Error(\n        `Unknown attribute \"${options.sourceKey}\" passed as sourceKey, define this attribute on model \"${source.name}\" first`,\n      );\n    }\n\n    if ('keyType' in options) {\n      throw new TypeError(\n        'Option \"keyType\" has been removed from the BelongsTo\\'s options. Set \"foreignKey.type\" instead.',\n      );\n    }\n\n    if ('through' in options) {\n      throw new Error(\n        'The \"through\" option is not available in hasMany. N:M associations are defined using belongsToMany instead.',\n      );\n    }\n\n    super(secret, source, target, options, parent);\n\n    this.inverse =\n      inverse ??\n      BelongsToAssociation.associate(\n        secret,\n        target,\n        source,\n        removeUndefined({\n          as: options.inverse?.as,\n          scope: options.inverse?.scope,\n          foreignKey: options.foreignKey,\n          targetKey: options.sourceKey,\n          foreignKeyConstraints: options.foreignKeyConstraints,","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/sequelize/sequelize/blob/7e1deec499d5afbb8d1877c2f4d545cead1214ec/packages/core/src/associations/has-many.ts#L110-L146","documentation":"Many-to-many (N:M) associations must use `belongsToMany`, not `hasMany`. The HasMany constructor (has-many.ts:127) checks `'through' in options` and throws an Error if present, because a `through` option on hasMany is a common mistake — hasMany is one-to-many with a foreign key on the target, not a join table.","triggerScenarios":"`User.hasMany(Project, { through: 'UserProject' })` — passing a join table to a one-to-many association.","commonSituations":"Coming from Sequelize v3/v4 where `hasMany` with `through` was historically (mis)used; confusing one-to-many with many-to-many; copy-pasting from a belongsToMany example.","solutions":["Switch to `belongsToMany`: `User.belongsToMany(Project, { through: 'UserProject' })`.","If you truly want one-to-many, remove the `through` option and let the FK live on the target."],"exampleFix":"// before\nUser.hasMany(Project, { through: 'UserProject' });\n\n// after\nUser.belongsToMany(Project, { through: 'UserProject' });","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isHasManyWithThrough(options) {\n  return 'through' in options;\n}\nif (isHasManyWithThrough(opts)) {\n  throw new Error('hasMany does not support through; use belongsToMany for N:M');\n}","tryCatchPattern":null,"preventionTips":["Reserve `through` exclusively for belongsToMany.","When migrating from old Sequelize, audit hasMany calls for `through`.","Use TypeScript — HasManyOptions intentionally omits `through`."],"tags":["has-many","through","belongs-to-many","association-type","migration"],"analyzedSha":"7e1deec499d5afbb8d1877c2f4d545cead1214ec","analyzedAt":"2026-08-03T18:58:44.549Z","schemaVersion":2}