fatedier/frp · error
unknown plugin type: %s
Error message
unknown plugin type: %s
What it means
DecodeClientPluginOptionsJSON resolves the plugin options struct from clientPluginOptionsTypeMap keyed by the envelope "type". An empty type is rejected earlier ('plugin type is empty'), so this error means a non-empty type string that has no registered options struct — an unknown or unsupported client plugin type. Raised before any plugin field decoding.
Source
Thrown at pkg/config/v1/decode.go:115
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")
}
v, ok := clientPluginOptionsTypeMap[env.Type]
if !ok {
return TypedClientPluginOptions{}, fmt.Errorf("unknown plugin type: %s", env.Type)
}
optionsStruct := reflect.New(v).Interface().(ClientPluginOptions)
if err := decodeJSONWithOptions(b, optionsStruct, options); err != nil {
return TypedClientPluginOptions{}, fmt.Errorf("unmarshal ClientPluginOptions error: %v", err)
}
return TypedClientPluginOptions{
Type: env.Type,
ClientPluginOptions: optionsStruct,
}, 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 {View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Use the exact documented plugin type identifier (snake_case, e.g. static_file, http2https, socks5).
- Upgrade (or align) the frp build so the plugin is registered in clientPluginOptionsTypeMap.
- Check for stray whitespace or capitalization in the type string.
Example fix
// before
{"type": "Static_File", "localPath": "/srv"}
// after
{"type": "static_file", "localPath": "/srv"} Defensive patterns
Strategy: validation
Validate before calling
// Validate plugin type against the registry before decoding.
func validClientPluginName(t string) error {
if strings.TrimSpace(t) == "" {
return fmt.Errorf("plugin type is empty")
}
if !isKnownClientPlugin(strings.TrimSpace(t)) {
return fmt.Errorf("unknown plugin type %q", t)
}
return nil
} Type guard
func isKnownClientPlugin(t string) bool {
switch t {
case "static_file", "http2https", "https2http", "unix_domain_socket", "socks5", "proxy_protocol":
return true
}
return false
} Try / catch
if _, err := v1.DecodeClientPluginOptionsJSON(b, opts); err != nil {
if strings.Contains(err.Error(), "unknown plugin type") {
// correct the type string or upgrade frp to a build that registers it
}
return err
} Prevention
- Copy plugin names verbatim from the frp docs (snake_case).
- Align plugin usage with the deployed frp version.
- Trim whitespace in generated type strings.
When it happens
Trigger: Calling DecodeClientPluginOptionsJSON with {"type":"webdav2",...} where webdav2 is not registered, or a type with whitespace/case drift like "Static_File". Any non-empty plugin type absent from the registry for this binary.
Common situations: Plugins added in newer frp releases used against an older frp; community forks with extra plugins; typos or casing differences in plugin names in generated configs.
Related errors
- unmarshal proxy plugin error: %v
- unmarshal visitor plugin error: %v
- unmarshal ClientPluginOptions error: %v
- unknown visitor plugin type: %s
- unmarshal VisitorPluginOptions error: %v
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/fc856b888bbf4c34.
Report an issue: GitHub.