{"record":{"id":"1bd8e96f23441b7b","repo":"fatedier/frp","slug":"unmarshal-visitor-plugin-error-v","errorCode":null,"errorMessage":"unmarshal visitor plugin error: %v","messagePattern":"unmarshal visitor plugin error: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/v1/decode.go","lineNumber":93,"sourceCode":"\t}\n\n\tvar env typedEnvelope\n\tif err := jsonx.Unmarshal(b, &env); err != nil {\n\t\treturn nil, err\n\t}\n\n\tconfigurer := NewVisitorConfigurerByType(VisitorType(env.Type))\n\tif configurer == nil {\n\t\treturn nil, fmt.Errorf(\"unknown visitor type: %s\", env.Type)\n\t}\n\tif err := decodeJSONWithOptions(b, configurer, options); err != nil {\n\t\treturn nil, fmt.Errorf(\"unmarshal VisitorConfig error: %v\", err)\n\t}\n\n\tif len(env.Plugin) > 0 && !isJSONNull(env.Plugin) {\n\t\tplugin, err := DecodeVisitorPluginOptionsJSON(env.Plugin, options)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unmarshal visitor plugin error: %v\", err)\n\t\t}\n\t\tconfigurer.GetBaseConfig().Plugin = plugin\n\t}\n\treturn configurer, nil\n}\n\nfunc DecodeClientPluginOptionsJSON(b []byte, options DecodeOptions) (TypedClientPluginOptions, error) {\n\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}","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/v1/decode.go#L75-L111","documentation":"After the visitor's own fields decode, DecodeVisitorConfigurerJSON decodes an embedded \"plugin\" object with DecodeVisitorPluginOptionsJSON. If that block's type is not in visitorPluginOptionsTypeMap, or its fields fail to decode, the error is wrapped as 'unmarshal visitor plugin error'. Visitor plugin support is far narrower than proxy plugin support, so this is commonly hit by unsupported plugin types.","triggerScenarios":"A visitors[] entry with a plugin block whose type is anything but the supported visitor plugin (e.g. trying static_file or an arbitrary proxy plugin inside a visitor), or wrong-typed plugin option fields. Triggered via DecodeClientConfigJSON or direct DecodeVisitorConfigurerJSON.","commonSituations":"Assuming any proxy plugin works inside a visitor; only specific plugins (e.g. the encryption plugin family) are valid for visitors; plugin blocks accidentally left behind when converting a proxy into a visitor.","solutions":["Remove the plugin block from the visitor, or use only plugins registered in visitorPluginOptionsTypeMap for this frp build.","Fix the wrapped field-level error if the plugin type is valid but an option has the wrong type/name.","If you need a proxy plugin, move the config to a proxies[] entry instead of a visitor."],"exampleFix":"// before\n[[visitors]]\ntype = \"stcp\"\n[visitors.plugin]\ntype = \"static_file\"\n\n// after\n[[visitors]]\ntype = \"stcp\"\n# (plugin removed; static_file is a proxy plugin, not a visitor plugin)","handlingStrategy":"validation","validationCode":"// Visitor plugin support is a small allowlist; check before shipping configs.\nfunc hasVisitorPluginSupport(pluginType string) bool {\n\t// visitorPluginOptionsTypeMap is intentionally tiny; treat anything else as unsupported\n\treturn false // replace with the allowlist for your frp version\n}","typeGuard":"func isVisitorPluginCandidate(t string) bool {\n\t// enumerate visitorPluginOptionsTypeMap keys for your frp build here\n\treturn false\n}","tryCatchPattern":"if _, err := v1.DecodeClientConfigJSON(b, opts); err != nil {\n\tif strings.Contains(err.Error(), \"unmarshal visitor plugin error\") {\n\t\t// remove the plugin block from the visitor or move it to a proxy\n\t}\n\treturn err\n}","preventionTips":["Don't put proxy plugins inside visitor entries.","Check visitorPluginOptionsTypeMap for your version before using visitor plugins.","Prefer plain visitors unless a visitor plugin is explicitly documented."],"tags":["go","frp","config","json","visitor","plugin","decode"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}