d2lang/d2 · error

cannot connect to reserved keyword

Error message

cannot connect to reserved keyword

What it means

Object.Connect in d2graph refuses to create an edge whose source or destination id segment is an unquoted d2 reserved keyword (like `shape`, `label`, `style`, etc.). Reserved keywords have special meaning in keys, so using one unquoted as an endpoint is ambiguous and rejected.

Source

Thrown at d2graph/d2graph.go:1327

		}
		commonIDA = append(commonIDA, srcIDA[0])
		srcIDA = srcIDA[1:]
		dstIDA = dstIDA[1:]
	}

	commonKey := ""
	if len(commonIDA) > 0 {
		commonKey = strings.Join(commonIDA, ".") + "."
	}

	return fmt.Sprintf("%s(%s %s %s)[%d]", commonKey, strings.Join(srcIDA, "."), e.ArrowString(), strings.Join(dstIDA, "."), e.Index)
}

func (obj *Object) Connect(srcID, dstID []d2ast.String, srcArrow, dstArrow bool, label string) (*Edge, error) {
	for _, id := range [][]d2ast.String{srcID, dstID} {
		for _, p := range id {
			if _, ok := d2ast.ReservedKeywords[p.ScalarString()]; ok && p.IsUnquoted() {
				return nil, errors.New("cannot connect to reserved keyword")
			}
		}
	}

	src := obj.ensureChildEdge(srcID)
	dst := obj.ensureChildEdge(dstID)

	e := &Edge{
		Attributes: Attributes{
			Label: Scalar{
				Value: label,
			},
		},
		Src:      src,
		SrcArrow: srcArrow,
		Dst:      dst,
		DstArrow: dstArrow,
	}

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Rename the node to a non-reserved identifier.
  2. If the name must stay, use a quoted string for that segment (d2ast String with IsUnquoted false) so it is treated literally.
  3. Check the identifier against d2ast.ReservedKeywords before calling Connect in generated code.

Example fix

// before
connect(src: "label", dst: "a")
// after
connect(src: "'label'", dst: "a") // quoted, or rename the node
Defensive patterns

Strategy: validation

Validate before calling

for _, seg := range []string{srcName, dstName} {
  if _, reserved := d2ast.ReservedKeywords[seg]; reserved { seg = "'" + seg + "'" }
}

Type guard

func isReservedUnquoted(seg d2ast.String) bool {
  _, ok := d2ast.ReservedKeywords[seg.ScalarString()]
  return ok && seg.IsUnquoted()
}

Try / catch

if _, err := obj.Connect(srcID, dstID, false, false, ""); err != nil && err.Error() == "cannot connect to reserved keyword" {
  // quote the offending segments and retry
}

Prevention

When it happens

Trigger: Calling obj.Connect (directly or via d2oracle Set) where srcID or dstID contains an unquoted segment matching d2ast.ReservedKeywords, e.g. connecting to an object literally named `label` without quoting.

Common situations: Naming nodes after d2 keywords (`columns`, `rows`, `shape`, `label`) and then connecting them; programmatic graph building that doesn't quote identifiers.

Related errors


AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31). Data as JSON: /api/errors/986906da471790d6. Report an issue: GitHub.