nocobase/nocobase · error
HasMany association ${hasManyKey} cannot be used with Belong
Error message
HasMany association ${hasManyKey} cannot be used with BelongsToMany association ${association.target.name} with same through model What it means
NocoBase's UpdateGuard (checkValues → dfs) rejects update values that simultaneously reference a HasMany relation and a BelongsToMany relation whose target model shares the same through table. Updating both would write conflicting rows into the same join table, producing duplicate/corrupt association data, so the write is blocked up front.
Source
Thrown at packages/core/database/src/update-guard.ts:99
const belongsToManyValueKeys = associationValueKeys.filter((key) => {
return associations[key].associationType === 'BelongsToMany';
});
const hasManyValueKeys = associationValueKeys.filter((key) => {
return associations[key].associationType === 'HasMany';
});
for (const belongsToManyKey of belongsToManyValueKeys) {
const association = associations[belongsToManyKey];
const through = association.through.model;
belongsToManyThroughNames.push(through.name);
}
for (const hasManyKey of hasManyValueKeys) {
const association = associations[hasManyKey];
if (belongsToManyThroughNames.includes(association.target.name)) {
throw new Error(
`HasMany association ${hasManyKey} cannot be used with BelongsToMany association ${association.target.name} with same through model`,
);
}
}
};
dfs(values, this.model);
}
/**
* Sanitize values by whitelist blacklist
* @param values
*/
sanitize(values: UpdateValues) {
if (values === null || values === undefined) {
return values;
}
values = lodash.clone(values);View on GitHub (pinned to fa42722fef)
Solutions
- Remove the HasMany-on-through-model key (or the BelongsToMany key) from the values payload — update them one at a time
- Fix the collection schema so only one association flavor targets the given through model
- Strip reverse/redundant relation keys server-side before passing values to repository.update
- If both are needed, use two sequential updates instead of one combined payload
Example fix
// before
await repo.update({ filterByTk: 1, values: { tags: [1,2], postTags: [{ id: 9, tagId: 2 }] } });
// after
await repo.update({ filterByTk: 1, values: { tags: [1,2] } }); // manage through-model via BelongsToMany only Defensive patterns
Strategy: validation
Validate before calling
function hasThroughConflict(values, model) {
const keys = Object.keys(values);
const btmTargets = keys.filter(k => model.associations[k]?.associationType === 'BelongsToMany')
.map(k => model.associations[k].target.name);
return keys.some(k => model.associations[k]?.associationType === 'HasMany' && btmTargets.includes(model.associations[k].target.name));
} Type guard
function isSafeUpdateValues(values: Record<string, unknown>, model: any): boolean {
return !hasThroughConflict(values, model);
} Try / catch
try {
await repo.update(options);
} catch (e) {
if (e.message.includes('cannot be used with BelongsToMany')) {
// split into sequential updates without the conflicting HasMany key
} else throw e;
} Prevention
- Keep one association flavor per through model in collection schemas
- Strip inverse HasMany-on-through keys from client-submitted values
- Review generated forms for duplicated relation keys
- Add server-side payload sanitization for relation keys
When it happens
Trigger: Sending a values payload (repository.update / create with values) on a model that has both e.g. posts.tags (BelongsToMany, through='post_tags') and posts.postTags (HasMany on the through model), and including both keys in the same values object.
Common situations: Schema/form collections that auto-generate both association flavors for the same through collection; clients copying full record JSON (including inverse relation keys) back into an update payload.
Related errors
- association ${appendFields[0]} in ${model.name} not found
- association ${appendAssociation} in ${model.name} not found
- target collection for association ${appendAssociation} in ${
- ${targetKey} field value is empty
- parser.associationNotFoundWarnings.join('; ')
AI-assisted analysis of nocobase/nocobase@fa42722fef (2026-09-01).
Data as JSON: /api/errors/04a076b27b1529b5.
Report an issue: GitHub.