MHSanaei/3x-ui · error

invalid type for user field %q: %T

Error message

invalid type for user field %q: %T

What it means

The required-field variant of the type check in getRequiredUserString: the key exists and is non-nil, but its value is not a Go string (e.g. a float64 from JSON numbers, a bool, or a nested map). The %T verb in the message tells you the concrete type that arrived, which pinpoints the serialization mistake upstream.

Source

Thrown at internal/xray/api.go:68

// XrayAPI is a gRPC client for managing Xray core configuration, inbounds, outbounds, and statistics.
type XrayAPI struct {
	HandlerServiceClient *command.HandlerServiceClient
	StatsServiceClient   *statsService.StatsServiceClient
	RoutingServiceClient *routerService.RoutingServiceClient
	grpcClient           *grpc.ClientConn
	isConnected          bool
	StatsLastValues      map[string]int64
}

func getRequiredUserString(user map[string]any, key string) (string, error) {
	value, ok := user[key]
	if !ok || value == nil {
		return "", fmt.Errorf("missing required user field %q", key)
	}

	strValue, ok := value.(string)
	if !ok {
		return "", fmt.Errorf("invalid type for user field %q: %T", key, value)
	}

	return strValue, nil
}

func getOptionalUserString(user map[string]any, key string) (string, error) {
	value, ok := user[key]
	if !ok || value == nil {
		return "", nil
	}

	strValue, ok := value.(string)
	if !ok {
		return "", fmt.Errorf("invalid type for user field %q: %T", key, value)
	}

	return strValue, nil
}

View on GitHub (pinned to ad32144c42)

Solutions

  1. Match the %T in the message to the offending field and fix the producer: ensure the JSON value is a string (quote numbers/booleans where a string is expected).
  2. Correct the stored client settings via the panel editor (re-enter the field) so the regenerated map has the right type.
  3. Add upstream schema validation (Zod/JSON schema on the inbound settings) so type-wrong clients are rejected at the API boundary with a precise path instead of deep inside Xray user building.

Example fix

// before
user := map[string]any{"email": 12345}

// after
user := map[string]any{"email": "12345"}
Defensive patterns

Strategy: type-guard

Validate before calling

// Coerce/validate string fields before passing the map to the Xray builder
for k, v := range user {
    if s, ok := v.(string); ok {
        user[k] = strings.TrimSpace(s)
    }
}

Type guard

func allUserStrings(user map[string]any, keys ...string) error {
    for _, k := range keys {
        if v, ok := user[k]; ok && v != nil {
            if _, isStr := v.(string); !isStr {
                return fmt.Errorf("field %q must be a string, got %T", k, v)
            }
        }
    }
    return nil
}

Try / catch

if strings.Contains(err.Error(), "invalid type for user field") {
    // %T in message names the actual type; fix producer serialization, no retry
}

Prevention

When it happens

Trigger: Building a user map from JSON where a string field was encoded as a number or boolean — e.g. "email": 123, or "publicKey": {"b64": "..."} — and passing it into the Xray account builder that calls getRequiredUserString.

Common situations: Generated/imported client JSON from external tools with wrong types; a frontend or API consumer sending form values that get coerced to numbers; refactors that changed a field's Go type while stale clients still hold the old shape in the DB.

Related errors


AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15). Data as JSON: /api/errors/59149ed79ba993a5. Report an issue: GitHub.