{"record":{"id":"fd50003bc428479b","repo":"fatedier/frp","slug":"unmarshal-clientpluginoptions-error-v","errorCode":null,"errorMessage":"unmarshal ClientPluginOptions error: %v","messagePattern":"unmarshal ClientPluginOptions error: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/v1/decode.go","lineNumber":119,"sourceCode":"\tif isJSONNull(b) {\n\t\treturn TypedClientPluginOptions{}, nil\n\t}\n\n\tvar env typedEnvelope\n\tif err := jsonx.Unmarshal(b, &env); err != nil {\n\t\treturn TypedClientPluginOptions{}, err\n\t}\n\tif env.Type == \"\" {\n\t\treturn TypedClientPluginOptions{}, errors.New(\"plugin type is empty\")\n\t}\n\n\tv, ok := clientPluginOptionsTypeMap[env.Type]\n\tif !ok {\n\t\treturn TypedClientPluginOptions{}, fmt.Errorf(\"unknown plugin type: %s\", env.Type)\n\t}\n\toptionsStruct := reflect.New(v).Interface().(ClientPluginOptions)\n\tif err := decodeJSONWithOptions(b, optionsStruct, options); err != nil {\n\t\treturn TypedClientPluginOptions{}, fmt.Errorf(\"unmarshal ClientPluginOptions error: %v\", err)\n\t}\n\treturn TypedClientPluginOptions{\n\t\tType:                env.Type,\n\t\tClientPluginOptions: optionsStruct,\n\t}, nil\n}\n\nfunc DecodeVisitorPluginOptionsJSON(b []byte, options DecodeOptions) (TypedVisitorPluginOptions, error) {\n\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\")","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/v1/decode.go#L101-L137","documentation":"DecodeClientPluginOptionsJSON recognized the plugin type and reflect-constructed its options struct, but decodeJSONWithOptions failed to unmarshal the plugin JSON into that struct. The underlying error is wrapped as 'unmarshal ClientPluginOptions error'. It identifies a field-level mismatch inside the plugin options object, not a bad type.","triggerScenarios":"A plugin options object with a wrong-typed field (e.g. static_file's localPath given as a number, or an array where a string is expected), or unknown fields under strict DecodeOptions. Any DecodeClientPluginOptionsJSON call whose body doesn't match the struct for the named plugin.","commonSituations":"Plugin option renames across frp versions (INI-era hyphenated names like plugin_local_path vs TOML-era localPath); hand-written JSON with quoted numbers; strict decoding flagging legacy keys.","solutions":["Follow the wrapped error to the exact field; correct its value type or name for that plugin's schema.","Use current option names (e.g. localPath for static_file) rather than legacy hyphenated keys.","If strict mode is on and the field is obsolete, delete it instead of suppressing the error."],"exampleFix":"// before\n{\"type\": \"static_file\", \"localPath\": 8080}\n\n// after\n{\"type\": \"static_file\", \"localPath\": \"/srv/files\"}","handlingStrategy":"try-catch","validationCode":"// Optional schema lint for the most common mistake: string where number expected.\nfunc lintPluginOptions(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{\"localPort\", \"remotePort\", \"bindPort\", \"httpsPort\"} {\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.DecodeClientPluginOptionsJSON(b, opts); err != nil {\n\tif strings.Contains(err.Error(), \"unmarshal ClientPluginOptions error\") {\n\t\t// wrapped error names the field; fix type/name in the plugin options object\n\t}\n\treturn err\n}","preventionTips":["Use current camelCase option names (localPath, not plugin_local_path).","Keep numbers as JSON numbers.","When strict decoding is enabled, remove obsolete keys."],"tags":["go","frp","config","json","plugin","decode","type-mismatch"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}