fatedier/frp · error
unmarshal visitor plugin error: %v
Error message
unmarshal visitor plugin error: %v
What it means
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.
Source
Thrown at pkg/config/v1/decode.go:93
}
var env typedEnvelope
if err := jsonx.Unmarshal(b, &env); err != nil {
return nil, err
}
configurer := NewVisitorConfigurerByType(VisitorType(env.Type))
if configurer == nil {
return nil, fmt.Errorf("unknown visitor type: %s", env.Type)
}
if err := decodeJSONWithOptions(b, configurer, options); err != nil {
return nil, fmt.Errorf("unmarshal VisitorConfig error: %v", err)
}
if len(env.Plugin) > 0 && !isJSONNull(env.Plugin) {
plugin, err := DecodeVisitorPluginOptionsJSON(env.Plugin, options)
if err != nil {
return nil, fmt.Errorf("unmarshal visitor plugin error: %v", err)
}
configurer.GetBaseConfig().Plugin = plugin
}
return configurer, nil
}
func DecodeClientPluginOptionsJSON(b []byte, options DecodeOptions) (TypedClientPluginOptions, error) {
if isJSONNull(b) {
return TypedClientPluginOptions{}, nil
}
var env typedEnvelope
if err := jsonx.Unmarshal(b, &env); err != nil {
return TypedClientPluginOptions{}, err
}
if env.Type == "" {
return TypedClientPluginOptions{}, errors.New("plugin type is empty")
}View on GitHub (pinned to 6c8a8d0a97)
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.
Example fix
// before [[visitors]] type = "stcp" [visitors.plugin] type = "static_file" // after [[visitors]] type = "stcp" # (plugin removed; static_file is a proxy plugin, not a visitor plugin)
Defensive patterns
Strategy: validation
Validate before calling
// Visitor plugin support is a small allowlist; check before shipping configs.
func hasVisitorPluginSupport(pluginType string) bool {
// visitorPluginOptionsTypeMap is intentionally tiny; treat anything else as unsupported
return false // replace with the allowlist for your frp version
} Type guard
func isVisitorPluginCandidate(t string) bool {
// enumerate visitorPluginOptionsTypeMap keys for your frp build here
return false
} Try / catch
if _, err := v1.DecodeClientConfigJSON(b, opts); err != nil {
if strings.Contains(err.Error(), "unmarshal visitor plugin error") {
// remove the plugin block from the visitor or move it to a proxy
}
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- unknown visitor plugin type: %s
- 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/1bd8e96f23441b7b.
Report an issue: GitHub.