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

  1. Remove the HasMany-on-through-model key (or the BelongsToMany key) from the values payload — update them one at a time
  2. Fix the collection schema so only one association flavor targets the given through model
  3. Strip reverse/redundant relation keys server-side before passing values to repository.update
  4. 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

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


AI-assisted analysis of nocobase/nocobase@fa42722fef (2026-09-01). Data as JSON: /api/errors/04a076b27b1529b5. Report an issue: GitHub.