{"record":{"id":"5a56bcafc95d75a0","repo":"ent/ent","slug":"edges-cannot-be-required-in-both-directions-s-s","errorCode":null,"errorMessage":"edges cannot be required in both directions: %s.%s <-> %s.%s","messagePattern":"edges cannot be required in both directions: (.+?)\\.(.+?) <-> (.+?)\\.(.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"entc/gen/graph.go","lineNumber":424,"sourceCode":"//\t - A have an edge (E) to B (not unique), and B have a back-reference unique edge (E') for E.\n//\n//\tM2O (The \"Many\" side, holds the reference to the \"One\" side).\n//\t - A have a unique edge (E) to B, and B doesn't have a back-reference edge for E.\n//\t - A have a unique edge (E) to B, and B have a back-reference non-unique edge (E') for E.\n//\n//\tM2M\n//\t - A have an edge (E) to B (not unique), and B have a back-reference non-unique edge (E') for E.\n//\t - A have an edge (E) to A (not unique).\nfunc (g *Graph) resolve(t *Type) error {\n\tfor _, e := range t.Edges {\n\t\tswitch {\n\t\tcase e.IsInverse():\n\t\t\tref, ok := e.Type.HasAssoc(e.Inverse)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"edge %q is missing for inverse edge: %s.%s(%s)\", e.Inverse, t.Name, e.Name, e.Type.Name)\n\t\t\t}\n\t\t\tif !e.Optional && !ref.Optional {\n\t\t\t\treturn fmt.Errorf(\"edges cannot be required in both directions: %s.%s <-> %s.%s\", t.Name, e.Name, e.Type.Name, ref.Name)\n\t\t\t}\n\t\t\tif ref.Type != t {\n\t\t\t\treturn fmt.Errorf(\"mismatch type for back-ref %q of %s.%s <-> %s.%s\", e.Inverse, t.Name, e.Name, e.Type.Name, ref.Name)\n\t\t\t}\n\t\t\te.Ref, ref.Ref = ref, e\n\t\t\ttable := t.Table()\n\t\t\t// Name the foreign-key column in a format that wouldn't change even if an inverse\n\t\t\t// edge is dropped (or added). The format is: \"<Edge-Owner>_<Edge-Name>\".\n\t\t\tcolumn := fmt.Sprintf(\"%s_%s\", e.Type.Label(), snake(ref.Name))\n\t\t\tswitch a, b := ref.Unique, e.Unique; {\n\t\t\t// If the relation column is in the inverse side/table. The rule is simple, if assoc is O2M,\n\t\t\t// then inverse is M2O and the relation is in its table.\n\t\t\tcase a && b:\n\t\t\t\te.Rel.Type, ref.Rel.Type = O2O, O2O\n\t\t\tcase !a && b:\n\t\t\t\te.Rel.Type, ref.Rel.Type = M2O, O2M\n\n\t\t\t// If the relation column is in the assoc side.","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/ent/ent/blob/69d5d4deb19599f129166634e09d33addcf3f2cc/entc/gen/graph.go#L406-L442","documentation":"ent requires that at least one side of an edge pair is optional. When resolving edges, if both the inverse edge and its referenced association edge are declared required (not Optional/Ref without .Optional()), codegen fails because a mandatory relation in both directions cannot be satisfied when inserting rows. This is a schema-validation error raised by Graph.resolve.","triggerScenarios":"Both schemas declare the edge as required, e.g. edge.From(\"owner\", \"User\").Field(\"owner_id\").Ref(\"pets\") (no .Optional()) while User has edge.To(\"pets\", Pet.Type) also without .Optional(), for a non-M2M relation backed by a NOT NULL foreign key.","commonSituations":"Copy-pasting edge definitions and forgetting .Optional() on one side; converting an optional relation to required on both ends while tightening constraints; M2M edges are exempt (always optional), so this hits O2M/M2M-by-FK and O2O pairs.","solutions":["Add .Optional() to one side of the edge (usually the many/inverse side) so at least one direction is optional","If the FK column should allow NULL, also keep .Field(\"owner_id\") nullable in the column definition","Decide which direction is semantically optional and mark only that one required","Regenerate code after the change"],"exampleFix":"// before (both required)\nedge.From(\"owner\", \"User\").Field(\"owner_id\").Ref(\"pets\")\n// after (one side optional)\nedge.From(\"owner\", \"User\").Field(\"owner_id\").Ref(\"pets\").Optional()","handlingStrategy":"validation","validationCode":"// audit schema: at least one side of each edge pair must be Optional\nfunc checkEdgePair(a, b Edge) error {\n    if !a.Optional && !b.Optional {\n        return fmt.Errorf(\"edges %s <-> %s both required\", a.Name, b.Name)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := entc.Generate(...); err != nil {\n    if strings.Contains(err.Error(), \"cannot be required in both directions\") {\n        log.Fatalf(\"add .Optional() to one side of the edge: %v\", err)\n    }\n    log.Fatalf(\"ent codegen failed: %v\", err)\n}","preventionTips":["Decide requiredness direction up front: typically the M2O side is required, the O2M side is optional","Never mark both sides of an O2O/O2M pair required; add .Optional() to the back-reference","Remember M2M edges are always optional; the rule only bites FK-backed edges"],"tags":["ent","codegen","schema-validation","orm"],"backgroundTag":"bidirectional-required-edge","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"}