ent/ent · error

StorageKey.Table is allowed only for M2M edges (got %s)

Error message

StorageKey.Table is allowed only for M2M edges (got %s)

What it means

In setStorageKey, a custom StorageKey annotation with a Table is only valid for many-to-many edges, because only M2M relations introduce a join table. Ent throws this when a Table is set on an edge whose relation type is not M2M.

Source

Thrown at entc/gen/type.go:2114

}

// OrderFieldName returns the function/option name for ordering by edge field.
func (e Edge) OrderFieldName() (string, error) {
	if !e.Unique {
		return "", fmt.Errorf("edge %q is not-unique", e.Name)
	}
	return fmt.Sprintf("By%sField", pascal(e.Name)), nil
}

// setStorageKey sets the storage-key option in the schema or fail.
func (e *Edge) setStorageKey() error {
	key, err := e.StorageKey()
	if err != nil || key == nil {
		return err
	}
	switch rel := e.Rel; {
	case key.Table != "" && rel.Type != M2M:
		return fmt.Errorf("StorageKey.Table is allowed only for M2M edges (got %s)", e.Rel.Type)
	case len(key.Columns) == 1 && rel.Type == M2M:
		return fmt.Errorf("%s edge have 2 columns. Use edge.Columns(to, from) instead", e.Rel.Type)
	case len(key.Columns) > 1 && rel.Type != M2M:
		return fmt.Errorf("%s edge does not have 2 columns. Use edge.Column(%s) instead", e.Rel.Type, key.Columns[0])
	}
	if key.Table != "" {
		e.Rel.Table = key.Table
	}
	if len(key.Columns) > 0 {
		e.Rel.Columns[0] = key.Columns[0]
	}
	if len(key.Columns) > 1 {
		e.Rel.Columns[1] = key.Columns[1]
	}
	return nil
}

// StorageKey returns the storage-key defined on the schema if exists.

View on GitHub (pinned to 69d5d4deb1)

Solutions

  1. Remove the Table field from the StorageKey annotation on non-M2M edges
  2. Move the Table annotation to the actual M2M edge
  3. If renaming the FK column is the goal, use Column(...) in the annotation instead

Example fix

// before
edge.To("posts", Post.Type).StorageKey(entsql.Annotation{Table: "user_posts"})
// after
edge.To("posts", Post.Type).StorageKey(entsql.Annotation{Column: "user_id"})
Defensive patterns

Strategy: validation

Validate before calling

ann := entsql.Annotation{Table: "user_posts"}
if ann.Table != "" && edge.Rel.Type != gen.M2M {
    return fmt.Errorf("Table only allowed on M2M edges")
}

Try / catch

if err := edge.SetStorageKey(); err != nil {
    return fmt.Errorf("storage-key config invalid: %w", err)
}

Prevention

When it happens

Trigger: Annotating an O2O, O2M or inverse edge with entsql.Annotation{Table: "..."} via .StorageKey(an) where the relation type resolves to O2O/O2M/M2O.

Common situations: Copy-pasting a join-table StorageKey from an M2M edge to a one-to-many edge; renaming a FK table on the wrong edge type; misunderstanding that non-M2M edges use the owner's table.

Related errors


AI-assisted analysis of ent/ent@69d5d4deb1 (2026-09-03). Data as JSON: /api/errors/a155a410417df487. Report an issue: GitHub.