fatedier/frp · error
unknown visitor plugin type: %s
Error message
unknown visitor plugin type: %s
What it means
DecodeVisitorPluginOptionsJSON resolves the visitor plugin options struct from visitorPluginOptionsTypeMap keyed by the envelope "type". Empty types are rejected earlier ('visitor plugin type is empty'); this error means a non-empty type with no registered visitor plugin options struct. The visitor plugin registry is much smaller than the client plugin registry, so most proxy plugin names fail here.
Source
Thrown at pkg/config/v1/decode.go:142
}, nil
}
func DecodeVisitorPluginOptionsJSON(b []byte, options DecodeOptions) (TypedVisitorPluginOptions, error) {
if isJSONNull(b) {
return TypedVisitorPluginOptions{}, nil
}
var env typedEnvelope
if err := jsonx.Unmarshal(b, &env); err != nil {
return TypedVisitorPluginOptions{}, err
}
if env.Type == "" {
return TypedVisitorPluginOptions{}, errors.New("visitor plugin type is empty")
}
v, ok := visitorPluginOptionsTypeMap[env.Type]
if !ok {
return TypedVisitorPluginOptions{}, fmt.Errorf("unknown visitor plugin type: %s", env.Type)
}
optionsStruct := reflect.New(v).Interface().(VisitorPluginOptions)
if err := decodeJSONWithOptions(b, optionsStruct, options); err != nil {
return TypedVisitorPluginOptions{}, fmt.Errorf("unmarshal VisitorPluginOptions error: %v", err)
}
return TypedVisitorPluginOptions{
Type: env.Type,
VisitorPluginOptions: optionsStruct,
}, nil
}
func DecodeClientConfigJSON(b []byte, options DecodeOptions) (ClientConfig, error) {
type rawClientConfig struct {
ClientCommonConfig
Proxies []jsonx.RawMessage `json:"proxies,omitempty"`
Visitors []jsonx.RawMessage `json:"visitors,omitempty"`
}
View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Use only visitor plugin types registered in this frp build (check visitorPluginOptionsTypeMap for your version).
- Remove the plugin block if the visitor doesn't need one.
- Move plugin-based tunneling into a proxies[] entry where the full client plugin set is available.
Example fix
// before
{"type": "static_file", "localPath": "/srv"} // passed to DecodeVisitorPluginOptionsJSON
// after
// pass it to DecodeClientPluginOptionsJSON from a proxy instead:
{"type": "tcp", "plugin": {"type": "static_file", "localPath": "/srv"}} Defensive patterns
Strategy: validation
Validate before calling
// Guard: most plugin types are proxy-only; verify before calling the visitor decoder.
func canDecodeVisitorPlugin(t string) error {
if !isVisitorPluginCandidate(strings.TrimSpace(t)) {
return fmt.Errorf("type %q is not a visitor plugin; use DecodeClientPluginOptionsJSON on a proxy instead", t)
}
return nil
} Type guard
func isVisitorPluginCandidate(t string) bool {
// enumerate visitorPluginOptionsTypeMap keys for your frp build here
return false
} Try / catch
if _, err := v1.DecodeVisitorPluginOptionsJSON(b, opts); err != nil {
if strings.Contains(err.Error(), "unknown visitor plugin type") {
// move the plugin to a proxy or drop it
}
return err
} Prevention
- Never assume proxy plugins work as visitor plugins.
- Route plugin decoding through the proxy path unless the type is a registered visitor plugin.
- Keep a version-pinned allowlist of visitor plugin types.
When it happens
Trigger: Calling DecodeVisitorPluginOptionsJSON with a type such as "static_file", "socks5", or any other plugin registered only in clientPluginOptionsTypeMap, or a misspelled visitor plugin name.
Common situations: Reusing a proxy's plugin block inside a visitor config; assuming plugin parity between proxies and visitors; fork-specific visitor plugins not present in the running build.
Related errors
- unmarshal visitor plugin error: %v
- unmarshal VisitorPluginOptions error: %v
- unmarshal proxy plugin error: %v
- unknown visitor type: %s
- unmarshal VisitorConfig error: %v
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/2b77f0a3deeb215f.
Report an issue: GitHub.