fatedier/frp · error
unknown visitor type: %s
Error message
unknown visitor type: %s
What it means
DecodeVisitorConfigurerJSON mirrors the proxy path: it reads the envelope "type" and calls NewVisitorConfigurerByType. If the type is not a registered visitor type (stcp, sudp, xtcp are the visitors' backing types), the factory returns nil and this error names the unknown visitor type. It fires before any field-level decoding.
Source
Thrown at pkg/config/v1/decode.go:84
}
configurer.GetBaseConfig().Plugin = plugin
}
return configurer, nil
}
func DecodeVisitorConfigurerJSON(b []byte, options DecodeOptions) (VisitorConfigurer, error) {
if isJSONNull(b) {
return nil, errors.New("type is required")
}
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{}, nilView on GitHub (pinned to 6c8a8d0a97)
Solutions
- Use a visitor-supported type: "stcp", "sudp", or "xtcp" in the visitors section.
- Verify the type string is lowercase and exactly spelled.
- Move plain tcp/udp entries to the proxies section — they cannot be visitors.
Example fix
// before [[visitors]] type = "tcp" // after [[visitors]] type = "stcp"
Defensive patterns
Strategy: validation
Validate before calling
// Only these types have visitor configurers.
func validVisitorType(t string) error {
switch t {
case "stcp", "sudp", "xtcp":
return nil
}
return fmt.Errorf("unknown visitor type %q; want stcp, sudp, or xtcp", t)
} Type guard
func isKnownVisitorType(t string) bool {
switch t {
case "stcp", "sudp", "xtcp":
return true
}
return false
} Try / catch
if _, err := v1.DecodeClientConfigJSON(b, opts); err != nil {
if strings.Contains(err.Error(), "unknown visitor type") {
// fix the visitors[] entry type; tcp/udp belong in proxies[]
}
return err
} Prevention
- Remember only stcp/sudp/xtcp support visitors.
- Don't clone proxy blocks into the visitors section unchanged.
- Keep visitor type strings lowercase.
When it happens
Trigger: Calling DecodeVisitorConfigurerJSON (or DecodeClientConfigJSON over a visitors[] entry) with type set to a proxy-only type like "tcp", a misspelled value like "STCP", or a plugin name. Only visitor-capable types resolve to a VisitorConfigurer.
Common situations: Copy-pasting a proxy block into the visitors section without changing type; assuming every proxy type has a visitor counterpart; case mismatches after config migration.
Related errors
- unmarshal VisitorConfig error: %v
- unmarshal visitor plugin error: %v
- unknown visitor plugin type: %s
- unmarshal VisitorPluginOptions error: %v
- decode visitor at index %d: %w
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/16ddb5a031e59146.
Report an issue: GitHub.