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
- Restructure the proto for form-bound endpoints: use scalars or the supported WKTs (wrappers, Timestamp, Duration, FieldMask, Value, Struct) for query-encoded fields
- Flatten nested messages into prefixed scalar fields (address_city=...) or switch the endpoint to POST with the JSON codec for structured data
- For arbitrary JSON-in-query, type the field as google.protobuf.Value or Struct, which ARE supported and accept JSON payloads
- 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
- Design query-bound protos with scalars and supported WKTs only
- Use POST + protojson codec for structured/nested request bodies
- Run a schema lint rule rejecting custom message fields on GET endpoints
- Document that form binding flattens messages; reserve custom messages for JSON APIs
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
- unsupported message type: %q
- invalid path: %q is not a message
- field already set for oneof %q
- too many values for field %q: %s
- parsing field %q: %w
AI-assisted analysis of go-kratos/kratos@668db92c2c (2026-08-16).
Data as JSON: /api/errors/ec3951fa6be69e16.
Report an issue: GitHub.