fatedier/frp · error

unmarshal proxy plugin error: %v

Error message

unmarshal proxy plugin error: %v

What it means

After a proxy's own fields decode successfully, DecodeProxyConfigurerJSON decodes the embedded "plugin" object with DecodeClientPluginOptionsJSON. If that plugin object has a "type" that is not in clientPluginOptionsTypeMap (unknown plugin), or its fields fail decoding, the error is wrapped as 'unmarshal proxy plugin error'. The proxy is otherwise valid; only its plugin stanza is broken.

Source

Thrown at pkg/config/v1/decode.go:65

	}

	var env typedEnvelope
	if err := jsonx.Unmarshal(b, &env); err != nil {
		return nil, err
	}

	configurer := NewProxyConfigurerByType(ProxyType(env.Type))
	if configurer == nil {
		return nil, fmt.Errorf("unknown proxy type: %s", env.Type)
	}
	if err := decodeJSONWithOptions(b, configurer, options); err != nil {
		return nil, fmt.Errorf("unmarshal ProxyConfig error: %v", err)
	}

	if len(env.Plugin) > 0 && !isJSONNull(env.Plugin) {
		plugin, err := DecodeClientPluginOptionsJSON(env.Plugin, options)
		if err != nil {
			return nil, fmt.Errorf("unmarshal proxy plugin error: %v", err)
		}
		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 {

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Set plugin.type to a supported plugin name exactly as documented (e.g. static_file, http2https, unix_domain_socket, socks5, https2http...).
  2. Fix the wrapped error's named plugin field (wrong type or renamed option).
  3. Upgrade frp if the plugin exists only in a newer release, or drop the plugin block.

Example fix

// before
{ "type": "tcp", "plugin": { "type": "staticfile", "localPath": "/srv" } }

// after
{ "type": "tcp", "plugin": { "type": "static_file", "localPath": "/srv" } }
Defensive patterns

Strategy: validation

Validate before calling

// Verify the proxy's plugin block type is registered before decoding the client config.
func validClientPluginType(pluginJSON []byte) error {
	var env struct{ Type string `json:"type"` }
	if err := json.Unmarshal(pluginJSON, &env); err != nil {
		return err
	}
	switch env.Type {
	case "static_file", "http2https", "https2http", "unix_domain_socket", "socks5", "proxy_protocol":
		return nil
	}
	return fmt.Errorf("plugin type %q not supported by this frp build", env.Type)
}

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.DecodeClientConfigJSON(b, opts); err != nil {
	if strings.Contains(err.Error(), "unmarshal proxy plugin error") {
		// inspect plugin.type and plugin fields of the indexed proxy
	}
	return err
}

Prevention

When it happens

Trigger: A proxy with a plugin block whose type is misspelled or unsupported in this build (e.g. plugin.type="staticfile" instead of "static_file", or a plugin not compiled in), or plugin option fields of the wrong type. Triggered via DecodeClientConfigJSON or direct DecodeProxyConfigurerJSON on such a proxy.

Common situations: Plugin name typos (static_file vs staticfile); using a plugin introduced in a newer frp version against an older binary; plugin options renamed between versions (e.g. plugin-local-path vs localPath after the TOML migration).

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/05d118d3ec1f0423. Report an issue: GitHub.