go-kratos/kratos · warning

unsupported message type: %q

Error message

unsupported message type: %q

What it means

Form DEcoder error for message-typed form fields: the field's message type full name is not in the supported well-known-type switch (Timestamp, Duration, the eight wrapper types, BytesValue, FieldMask, Value, Struct). Any other message type used as a singular form/query field hits the default and is rejected, because the codec can only render a message as a flat string for these special types.

Source

Thrown at encoding/form/proto_decode.go:328

		fm := &fieldmaskpb.FieldMask{}
		for _, fv := range strings.Split(value, ",") {
			fm.Paths = append(fm.Paths, jsonSnakeCase(fv))
		}
		msg = fm
	case "google.protobuf.Value":
		fm, err := structpb.NewValue(value)
		if err != nil {
			return protoreflect.Value{}, err
		}
		msg = fm
	case "google.protobuf.Struct":
		var v structpb.Struct
		if err := protojson.Unmarshal([]byte(value), &v); err != nil {
			return protoreflect.Value{}, err
		}
		msg = &v
	default:
		return protoreflect.Value{}, fmt.Errorf("unsupported message type: %q", string(md.FullName()))
	}
	return protoreflect.ValueOfMessage(msg.ProtoReflect()), nil
}

// jsonSnakeCase converts a camelCase identifier to a snake_case identifier,
// according to the protobuf JSON specification.
// references: https://github.com/protocolbuffers/protobuf-go/blob/master/encoding/protojson/well_known_types.go#L864
func jsonSnakeCase(s string) string {
	var builder strings.Builder
	builder.Grow(len(s))

	for i := 0; i < len(s); i++ { // proto identifiers are always ASCII
		c := s[i]
		if isASCIIUpper(c) {
			builder.WriteByte('_')
			c += 'a' - 'A' // convert to lowercase
		}
		builder.WriteByte(c)

View on GitHub (pinned to 668db92c2c)

Solutions

  1. Restructure the proto for form-bound endpoints: use scalars or the supported WKTs (wrappers, Timestamp, Duration, FieldMask, Value, Struct) for query-encoded fields
  2. Flatten nested messages into prefixed scalar fields (address_city=...) or switch the endpoint to POST with the JSON codec for structured data
  3. For arbitrary JSON-in-query, type the field as google.protobuf.Value or Struct, which ARE supported and accept JSON payloads
  4. If a specific custom message must be supported, contribute a case to parseMessage or pre-decode the string yourself before binding

Example fix

// before: message Address { string city = 1; } + field Address address = 1;
//         GET /x?address=Berlin -> unsupported message type: "pkg.Address"
// after: flatten scalars for query binding
//         string address_city = 1;   GET /x?address_city=Berlin
Defensive patterns

Strategy: validation

Validate before calling

// Ensure every message-typed field reachable from the form schema is a supported WKT
var formSupported = map[string]bool{
	"google.protobuf.Timestamp": true, "google.protobuf.Duration": true,
	"google.protobuf.BytesValue": true, "google.protobuf.FieldMask": true,
	"google.protobuf.Value": true, "google.protobuf.Struct": true,
}
func formBindable(msg protoreflect.Message) error {
	var err error
	msg.Descriptor().Fields().Range(func(fd protoreflect.FieldDescriptor) bool {
		if fd.Kind() == protoreflect.MessageKind {
			name := string(fd.Message().FullName())
			if !formSupported[name] && !strings.HasPrefix(name, "google.protobuf.") {
				err = fmt.Errorf("custom message field %q cannot be form-bound", fd.Name())
				return false
			}
		}
		return true
	})
	return err
}

Type guard

func isSupportedFormMessage(fullName protoreflect.FullName) bool {
	switch fullName {
	case "google.protobuf.Timestamp", "google.protobuf.Duration",
		"google.protobuf.BytesValue", "google.protobuf.FieldMask",
		"google.protobuf.Value", "google.protobuf.Struct",
		"google.protobuf.DoubleValue", "google.protobuf.FloatValue",
		"google.protobuf.Int64Value", "google.protobuf.Int32Value",
		"google.protobuf.UInt64Value", "google.protobuf.UInt32Value",
		"google.protobuf.BoolValue", "google.protobuf.StringValue":
		return true
	}
	return false
}

Try / catch

if err := binding.BindQuery(msg, q); err != nil {
	if strings.Contains(err.Error(), "unsupported message type") {
		// API design issue: restructure proto or switch endpoint to JSON body
		return errors.BadRequest("UNSUPPORTED_FIELD_TYPE", err.Error())
	}
}

Prevention

When it happens

Trigger: Binding a query param into a field typed as a custom message (e.g. `?address=...` where address is your own proto message), or a WKT not in the switch such as google.protobuf.Any, ListValue, google.protobuf.Empty; deeply nested messages beyond the WKT set are equally unsupported in the value position.

Common situations: Proto evolution turning a scalar into a structured message while the HTTP API still uses form/query binding; trying to reuse a JSON-oriented API shape over GET query params; frontend expecting nested JSON in a query value.

Related errors


AI-assisted analysis of go-kratos/kratos@668db92c2c (2026-08-16). Data as JSON: /api/errors/ec3951fa6be69e16. Report an issue: GitHub.