{"record":{"id":"a56c03ca9b878573","repo":"glanceapp/glance","slug":"invalid-body-type-must-be-either-json-or-strin","errorCode":null,"errorMessage":"invalid body type, must be either 'json' or 'string'","messagePattern":"invalid body type, must be either 'json' or 'string'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/glance/widget-custom-api.go","lineNumber":149,"sourceCode":"\treturn defaultValue\n}\n\nfunc (req *CustomAPIRequest) initialize() error {\n\tif req == nil || req.URL == \"\" {\n\t\treturn nil\n\t}\n\n\tif req.Body != nil {\n\t\tif req.Method == \"\" {\n\t\t\treq.Method = http.MethodPost\n\t\t}\n\n\t\tif req.BodyType == \"\" {\n\t\t\treq.BodyType = \"json\"\n\t\t}\n\n\t\tif req.BodyType != \"json\" && req.BodyType != \"string\" {\n\t\t\treturn errors.New(\"invalid body type, must be either 'json' or 'string'\")\n\t\t}\n\n\t\tswitch req.BodyType {\n\t\tcase \"json\":\n\t\t\tencoded, err := json.Marshal(req.Body)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"marshaling body: %v\", err)\n\t\t\t}\n\n\t\t\treq.bodyReader = bytes.NewReader(encoded)\n\t\tcase \"string\":\n\t\t\tbodyAsString, ok := req.Body.(string)\n\t\t\tif !ok {\n\t\t\t\treturn errors.New(\"body must be a string when body-type is 'string'\")\n\t\t\t}\n\n\t\t\treq.bodyReader = strings.NewReader(bodyAsString)\n\t\t}","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/glanceapp/glance/blob/91324e8de762702e97b0ac5c8e36271d644d8642/internal/glance/widget-custom-api.go#L131-L167","documentation":"In the Custom API widget, the .JSON \"key\" template function marshals a configured option to a JSON string. Unlike FloatOr/StringOr/BoolOr it has no default fallback: if the key is absent from the widget's options map it panics with 'key %q does not exist in options'. Go's template engine recovers the panic and reports it as a template execution error, so the widget render fails with that message.","triggerScenarios":"A template under template or template-partial calls .JSON \"foo\" but the widget config has no option foo: (renamed key, typo, or the option lives on a different widget instance).","commonSituations":"Refactoring: the template is updated to reference a new option key but glance.yml is not; copy-pasting a template partial that expects options the target widget does not define; case mismatches (Option vs option).","solutions":["Open the Custom API widget's config and add the missing key under options: with any YAML value","Check for typos/case differences between the template's .JSON argument and the options key","If the option is genuinely optional, use .StringOr/.FloatOr/.BoolOr with defaults, or guard in the template","Reload Glance config and re-render the widget to confirm the error clears"],"exampleFix":"// before (glance.yml)\n- type: custom-api\n  template: |\n    {{ .JSON \"webhook\" }}\n  options:\n    url: http://x\n// after: add the referenced key\n- type: custom-api\n  template: |\n    {{ .JSON \"webhook\" }}\n  options:\n    url: http://x\n    webhook: \"https://hooks.example/abc\"","handlingStrategy":"validation","validationCode":"// template-side guard before calling .JSON\n{{ if .Options.HasKey \"webhook\" }}{{ .JSON \"webhook\" }}{{ end }}","typeGuard":null,"tryCatchPattern":"Go's html/template recovers panics during Execute: capture the returned error and surface its message (which contains 'key %q does not exist in options') to the config author instead of crashing the server. Outside templates, check the map with `value, ok := options[key]; if !ok { ... }` before use.","preventionTips":["Keep templates and options in the same glance.yml file so key references stay in sync","For optional data prefer .StringOr/.FloatOr/.BoolOr which default instead of panicking","Lint custom templates by rendering them once with a full options fixture before deploy"],"tags":["go","custom-api","templates","config","panic"],"backgroundTag":null,"analyzedSha":"91324e8de762702e97b0ac5c8e36271d644d8642","analyzedAt":"2026-08-15T14:12:54.279Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}