{"record":{"id":"2b55fa93bc31c290","repo":"fatedier/frp","slug":"unmarshal-visitorpluginoptions-error-v","errorCode":null,"errorMessage":"unmarshal VisitorPluginOptions error: %v","messagePattern":"unmarshal VisitorPluginOptions error: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/v1/decode.go","lineNumber":146,"sourceCode":"\tif isJSONNull(b) {\n\t\treturn TypedVisitorPluginOptions{}, nil\n\t}\n\n\tvar env typedEnvelope\n\tif err := jsonx.Unmarshal(b, &env); err != nil {\n\t\treturn TypedVisitorPluginOptions{}, err\n\t}\n\tif env.Type == \"\" {\n\t\treturn TypedVisitorPluginOptions{}, errors.New(\"visitor plugin type is empty\")\n\t}\n\n\tv, ok := visitorPluginOptionsTypeMap[env.Type]\n\tif !ok {\n\t\treturn TypedVisitorPluginOptions{}, fmt.Errorf(\"unknown visitor plugin type: %s\", env.Type)\n\t}\n\toptionsStruct := reflect.New(v).Interface().(VisitorPluginOptions)\n\tif err := decodeJSONWithOptions(b, optionsStruct, options); err != nil {\n\t\treturn TypedVisitorPluginOptions{}, fmt.Errorf(\"unmarshal VisitorPluginOptions error: %v\", err)\n\t}\n\treturn TypedVisitorPluginOptions{\n\t\tType:                 env.Type,\n\t\tVisitorPluginOptions: optionsStruct,\n\t}, nil\n}\n\nfunc DecodeClientConfigJSON(b []byte, options DecodeOptions) (ClientConfig, error) {\n\ttype rawClientConfig struct {\n\t\tClientCommonConfig\n\t\tProxies  []jsonx.RawMessage `json:\"proxies,omitempty\"`\n\t\tVisitors []jsonx.RawMessage `json:\"visitors,omitempty\"`\n\t}\n\n\traw := rawClientConfig{}\n\tif err := decodeJSONWithOptions(b, &raw, options); err != nil {\n\t\treturn ClientConfig{}, err\n\t}","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/v1/decode.go#L128-L164","documentation":"DecodeVisitorPluginOptionsJSON recognized the visitor plugin type and constructed its options struct, but decodeJSONWithOptions failed to unmarshal the JSON body into it. The underlying error is wrapped as 'unmarshal VisitorPluginOptions error', pointing at a field-level mismatch inside the visitor plugin options object.","triggerScenarios":"A visitor plugin options object containing a wrong-typed field (string vs number) or unknown fields under strict DecodeOptions, passed to DecodeVisitorPluginOptionsJSON with a valid type.","commonSituations":"Hand-crafted visitor plugin JSON with legacy field names; schema drift between frp versions for visitor plugin options; quoted numbers from YAML pipelines.","solutions":["Use the wrapped error to locate the exact field; fix its name or JSON type.","Match the current visitor plugin schema for your frp version.","Remove legacy/unknown keys when strict decoding is enabled."],"exampleFix":"// before\n{\"type\": \"my_visitor_plugin\", \"bindPort\": \"6000\"}\n\n// after\n{\"type\": \"my_visitor_plugin\", \"bindPort\": 6000}","handlingStrategy":"try-catch","validationCode":"// Lint common numeric fields before decoding visitor plugin options.\nfunc lintVisitorPluginOptions(b []byte) error {\n\tvar probe map[string]any\n\tif err := json.Unmarshal(b, &probe); err != nil {\n\t\treturn err\n\t}\n\tfor _, numKey := range []string{\"bindPort\", \"port\"} {\n\t\tif v, ok := probe[numKey].(string); ok {\n\t\t\treturn fmt.Errorf(\"%s must be a number, got string %q\", numKey, v)\n\t\t}\n\t}\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":"if _, err := v1.DecodeVisitorPluginOptionsJSON(b, opts); err != nil {\n\tif strings.Contains(err.Error(), \"unmarshal VisitorPluginOptions error\") {\n\t\t// wrapped error names the field; correct its type or name\n\t}\n\treturn err\n}","preventionTips":["Match visitor plugin option schemas for your frp version.","Keep numeric fields numeric in generated JSON.","Remove unknown keys when strict decoding is on."],"tags":["go","frp","config","json","visitor","plugin","decode","type-mismatch"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}