fatedier/frp · error

unmarshal ProxyConfig error: %v

Error message

unmarshal ProxyConfig error: %v

What it means

DecodeProxyConfigurerJSON fails at decodeJSONWithOptions when the proxy object's fields cannot be unmarshalled into the typed ProxyConfigurer selected by "type". The underlying json unmarshal error is wrapped with 'unmarshal ProxyConfig error'. This means the type was recognized, but field names or values inside the proxy object do not match the struct for that proxy type.

Source

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

	Plugin jsonx.RawMessage `json:"plugin,omitempty"`
}

func DecodeProxyConfigurerJSON(b []byte, options DecodeOptions) (ProxyConfigurer, 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 := 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

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Read the wrapped error text — it names the exact offending field and reason; fix that field's type or name.
  2. Match the schema for the declared proxy type (e.g. tcp proxies take localPort/localIP as their proper types).
  3. If strict decoding rejected an unknown field, remove the stale field or disable strict mode in DecodeOptions.

Example fix

// before
{ "type": "tcp", "localPort": "80" }

// after
{ "type": "tcp", "localPort": 80 }
Defensive patterns

Strategy: try-catch

Validate before calling

// Optional pre-flight: strict-decode into a map to find unknown/mistyped fields first.
func lintProxyJSON(b []byte) error {
	var probe map[string]any
	if err := json.Unmarshal(b, &probe); err != nil {
		return err
	}
	if t, _ := probe["type"].(string); t == "" {
		return fmt.Errorf("proxy entry missing type")
	}
	return nil // full schema checks still happen in decodeJSONWithOptions
}

Try / catch

proxyCfg, err := v1.DecodeProxyConfigurerJSON(raw, opts)
if err != nil {
	if strings.Contains(err.Error(), "unmarshal ProxyConfig error") {
		// err wraps the json field error; log raw proxy JSON + err to pinpoint the field
		log.Printf("proxy decode failed: %v; payload: %s", err, raw)
	}
	return err
}

Prevention

When it happens

Trigger: A proxy entry with a field of the wrong JSON type (e.g. "localPort": "80" as string instead of number), an unknown field when strict decoding is enabled via DecodeOptions, or a structural mismatch such as passing an object where an array is expected. Reached through DecodeClientConfigJSON or a direct DecodeProxyConfigurerJSON call.

Common situations: Renamed/removed fields across frp major versions (INI -> TOML migration leftovers); strict-mode schemas (like EnableStrictDecoding options) rejecting unknown fields; YAML-to-JSON conversion quirks turning numbers into strings.

Related errors


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