{"record":{"id":"ee916feb85c67301","repo":"d2lang/d2","slug":"edge-key-must-be-reserved","errorCode":null,"errorMessage":"edge key must be reserved","messagePattern":"edge key must be reserved","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"d2oracle/edit.go","lineNumber":610,"sourceCode":"\t\t\tif earliestRef != nil && scope.Range.Before(earliestRef.MapKey.Range) {\n\t\t\t\t// Since the original mk was trimmed to common, we set to the edge that\n\t\t\t\t// the ref's scope is in\n\t\t\t\t// Only replace with earliestRef.Edge if it's not a glob pattern\n\t\t\t\tif !earliestRef.Edge.Src.HasGlob() && !earliestRef.Edge.Dst.HasGlob() {\n\t\t\t\t\tmk.Edges[0] = earliestRef.Edge\n\t\t\t\t}\n\t\t\t\t// We can't reference an edge before it's been defined\n\t\t\t\tearliestRef.Scope.InsertAfter(earliestRef.MapKey, mk)\n\t\t\t} else {\n\t\t\t\tappendMapKey(scope, mk)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\tattrs = edge.Attributes\n\n\t\tif mk.EdgeKey != nil {\n\t\t\tif _, ok := d2ast.ReservedKeywords[mk.EdgeKey.Path[0].Unbox().ScalarString()]; !ok {\n\t\t\t\treturn errors.New(\"edge key must be reserved\")\n\t\t\t}\n\t\t\treserved = true\n\n\t\t\ttoSkip = 1\n\t\t\tmk = &d2ast.Key{\n\t\t\t\tKey:   cloneKey(mk.EdgeKey),\n\t\t\t\tValue: mk.Value,\n\t\t\t}\n\n\t\t\tfoundMap := false\n\t\t\tfor _, ref := range refs {\n\t\t\t\t// TODO get the most nested one\n\t\t\t\tif ref.MapKey.Value.Map != nil {\n\t\t\t\t\tfoundMap = true\n\t\t\t\t\tscope = ref.MapKey.Value.Map\n\t\t\t\t\tfor _, n := range scope.Nodes {\n\t\t\t\t\t\tif n.MapKey == nil || n.MapKey.Value.Map == nil {\n\t\t\t\t\t\t\tcontinue","sourceCodeStart":592,"sourceCodeEnd":628,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2oracle/edit.go#L592-L628","documentation":"When a key addresses an edge's sub-key (`mk.EdgeKey`, e.g. the part after the edge in `(a -> b).style.opacity`), `_set` requires that sub-key to be a D2 reserved keyword (style, shape, icon, etc.). Arbitrary custom keys on edges are not allowed.","triggerScenarios":"Calling d2oracle.Set/Create with a key whose edge trailing key is not reserved, e.g. `(a -> b).color` or `(a -> b).myField` instead of `(a -> b).style.stroke`.","commonSituations":"Mistaking edge attributes for plain fields (writing `color` instead of `style.stroke`); porting object-level custom field syntax to edges; typos like `styel.opacity`.","solutions":["Use a reserved edge key: `style.<prop>` (stroke, opacity, etc.), `source-arrowhead.*`, or `target-arrowhead.*`.","Replace flat forms like `.color` with `.style.stroke`, `.fill` with `.style.fill`.","Check the key against d2ast.ReservedKeywords before calling.","For node-level custom fields remember this restriction applies only to the edge portion (mk.EdgeKey)."],"exampleFix":"// before\ng, err = d2oracle.Set(g, nil, \"(a -> b).color\", &red)\n// after\ng, err = d2oracle.Set(g, nil, \"(a -> b).style.stroke\", &red)","handlingStrategy":"validation","validationCode":"mk, err := d2parser.ParseMapKey(key)\nif err != nil { return err }\nif mk.EdgeKey != nil {\n    if _, ok := d2ast.ReservedKeywords[mk.EdgeKey.Path[0].Unbox().ScalarString()]; !ok {\n        return fmt.Errorf(\"%q is not a reserved edge key; use style.*, source-arrowhead.*, or target-arrowhead.*\", key)\n    }\n}","typeGuard":"func edgeKeyReserved(key string) bool {\n    mk, err := d2parser.ParseMapKey(key)\n    if err != nil || mk.EdgeKey == nil { return false }\n    _, ok := d2ast.ReservedKeywords[mk.EdgeKey.Path[0].Unbox().ScalarString()]\n    return ok\n}","tryCatchPattern":"err := d2oracle.Set(g, nil, key, &val)\nif err != nil && err.Error() == \"edge key must be reserved\" {\n    return fmt.Errorf(\"rewrite %q to use a reserved edge key like .style.stroke\", key)\n}","preventionTips":["Map intuitive names to reserved ones (color -> style.stroke, transparency -> style.opacity)","Check d2ast.ReservedKeywords when generating keys programmatically","Restrict edge attribute pickers in UIs to reserved keyword lists","Spell-check reserved segments (style, shape, icon, label) before calling"],"tags":["edge","reserved-keywords","api-misuse","d2"],"backgroundTag":"reserved-keyword-required","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}