{"record":{"id":"5a22d6fb8d8e4489","repo":"mikefarah/yq","slug":"cannot-substitute-with-v-can-only-substitute-str-5a22d6","errorCode":null,"errorMessage":"cannot substitute with %v, can only substitute strings. Hint: Most often you'll want to use '|=' over '=' for this operation","messagePattern":"cannot substitute with (.+?), can only substitute strings\\. Hint: Most often you'll want to use '\\|=' over '=' for this operation","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/yqlib/operator_strings.go","lineNumber":263,"sourceCode":"\tblock := expressionNode.RHS\n\n\tregExStr, replacementText, err := getSubstituteParameters(d, block, context)\n\n\tif err != nil {\n\t\treturn Context{}, err\n\t}\n\n\tregEx, err := regexp.Compile(regExStr)\n\tif err != nil {\n\t\treturn Context{}, err\n\t}\n\n\tvar results = list.New()\n\n\tfor el := context.MatchingNodes.Front(); el != nil; el = el.Next() {\n\t\tnode := el.Value.(*CandidateNode)\n\t\tif node.guessTagFromCustomType() != \"!!str\" {\n\t\t\treturn Context{}, fmt.Errorf(\"cannot substitute with %v, can only substitute strings. Hint: Most often you'll want to use '|=' over '=' for this operation\", node.Tag)\n\t\t}\n\n\t\tresult := node.CreateReplacement(substitute(node.Value, regEx, replacementText))\n\t\tresults.PushBack(result)\n\t}\n\n\treturn context.ChildContext(results), nil\n\n}\n\nfunc addMatch(original []*CandidateNode, match string, offset int, name string) []*CandidateNode {\n\n\tnewContent := append(original,\n\t\tcreateScalarNode(\"string\", \"string\"))\n\n\tif offset < 0 {\n\t\t// offset of -1 means there was no match, force a null value like jq\n\t\tnewContent = append(newContent,","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/mikefarah/yq/blob/8b5af0694bb82b41d4ae180fac9972029066f90a/pkg/yqlib/operator_strings.go#L245-L281","documentation":"substitute (sub) needs a string to run the regex replacement over; the per-node guard checks guessTagFromCustomType() != \"!!str\" and rejects the first non-string. The hint in the message points at the other common cause of seeing this: using '=' instead of '|=' makes the RHS context the whole node rather than the field's string value.","triggerScenarios":"Thrown at pkg/yqlib/operator_strings.go:263 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Use the update-assign form: .a |= sub(\"x\", \"y\") instead of .a = sub(\"x\", \"y\")","Convert to string: . | tostring | sub(...)","Ensure the target field is actually a string (not null/number)"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"8b5af0694bb82b41d4ae180fac9972029066f90a","analyzedAt":"2026-09-05T10:57:22.766Z","contentChangedAt":"2026-09-05T10:57:22.766Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}