{"record":{"id":"a155a410417df487","repo":"ent/ent","slug":"storagekey-table-is-allowed-only-for-m2m-edges-go","errorCode":null,"errorMessage":"StorageKey.Table is allowed only for M2M edges (got %s)","messagePattern":"StorageKey\\.Table is allowed only for M2M edges \\(got (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"entc/gen/type.go","lineNumber":2114,"sourceCode":"}\n\n// OrderFieldName returns the function/option name for ordering by edge field.\nfunc (e Edge) OrderFieldName() (string, error) {\n\tif !e.Unique {\n\t\treturn \"\", fmt.Errorf(\"edge %q is not-unique\", e.Name)\n\t}\n\treturn fmt.Sprintf(\"By%sField\", pascal(e.Name)), nil\n}\n\n// setStorageKey sets the storage-key option in the schema or fail.\nfunc (e *Edge) setStorageKey() error {\n\tkey, err := e.StorageKey()\n\tif err != nil || key == nil {\n\t\treturn err\n\t}\n\tswitch rel := e.Rel; {\n\tcase key.Table != \"\" && rel.Type != M2M:\n\t\treturn fmt.Errorf(\"StorageKey.Table is allowed only for M2M edges (got %s)\", e.Rel.Type)\n\tcase len(key.Columns) == 1 && rel.Type == M2M:\n\t\treturn fmt.Errorf(\"%s edge have 2 columns. Use edge.Columns(to, from) instead\", e.Rel.Type)\n\tcase len(key.Columns) > 1 && rel.Type != M2M:\n\t\treturn fmt.Errorf(\"%s edge does not have 2 columns. Use edge.Column(%s) instead\", e.Rel.Type, key.Columns[0])\n\t}\n\tif key.Table != \"\" {\n\t\te.Rel.Table = key.Table\n\t}\n\tif len(key.Columns) > 0 {\n\t\te.Rel.Columns[0] = key.Columns[0]\n\t}\n\tif len(key.Columns) > 1 {\n\t\te.Rel.Columns[1] = key.Columns[1]\n\t}\n\treturn nil\n}\n\n// StorageKey returns the storage-key defined on the schema if exists.","sourceCodeStart":2096,"sourceCodeEnd":2132,"githubUrl":"https://github.com/ent/ent/blob/69d5d4deb19599f129166634e09d33addcf3f2cc/entc/gen/type.go#L2096-L2132","documentation":"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.","triggerScenarios":"Annotating an O2O, O2M or inverse edge with entsql.Annotation{Table: \"...\"} via .StorageKey(an) where the relation type resolves to O2O/O2M/M2O.","commonSituations":"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.","solutions":["Remove the Table field from the StorageKey annotation on non-M2M edges","Move the Table annotation to the actual M2M edge","If renaming the FK column is the goal, use Column(...) in the annotation instead"],"exampleFix":"// before\nedge.To(\"posts\", Post.Type).StorageKey(entsql.Annotation{Table: \"user_posts\"})\n// after\nedge.To(\"posts\", Post.Type).StorageKey(entsql.Annotation{Column: \"user_id\"})","handlingStrategy":"validation","validationCode":"ann := entsql.Annotation{Table: \"user_posts\"}\nif ann.Table != \"\" && edge.Rel.Type != gen.M2M {\n    return fmt.Errorf(\"Table only allowed on M2M edges\")\n}","typeGuard":null,"tryCatchPattern":"if err := edge.SetStorageKey(); err != nil {\n    return fmt.Errorf(\"storage-key config invalid: %w\", err)\n}","preventionTips":["Only annotate join tables on M2M edges","Use Column(s), not Table, for FK naming on non-M2M edges","Review entsql annotations when changing relation cardinality"],"tags":["ent","code-generation","edges","storage-key","m2m"],"backgroundTag":"invalid-storage-key-annotation","analyzedSha":"69d5d4deb19599f129166634e09d33addcf3f2cc","analyzedAt":"2026-09-03T16:51:39.799Z","contentChangedAt":"2026-09-03T16:51:39.799Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}