labstack/echo · error
query/param/form tags are not allowed with anonymous struct
Error message
query/param/form tags are not allowed with anonymous struct field
What it means
Returned by bindData when an anonymous (embedded) struct field carries an explicit param/query/form/header tag. The binder descends into anonymous struct fields to bind their inner tagged fields, but cannot resolve which inner field a tag on the outer anonymous field refers to. Echo forbids binding tags directly on embedded struct fields to avoid this ambiguity.
Source
Thrown at bind.go:272
}
meta := bindMetaFor(typ)
for fi := range meta.fields { // iterate over all destination fields
fm := &meta.fields[fi]
structField := val.Field(fm.index)
if fm.anonymous {
if structField.Kind() == reflect.Pointer {
structField = structField.Elem()
}
}
if !structField.CanSet() {
continue
}
structFieldKind := structField.Kind()
inputFieldName := fm.tagName(tag)
if fm.anonymous && structFieldKind == reflect.Struct && inputFieldName != "" {
// if anonymous struct with query/param/form tags, report an error
return errors.New("query/param/form tags are not allowed with anonymous struct field")
}
if inputFieldName == "" {
// If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contain fields with tags).
// structs that implement BindUnmarshaler are bound only when they have explicit tag
if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {
if err := bindData(structField.Addr().Interface(), data, tag, dataFiles); err != nil {
return err
}
}
// does not have explicit tag and is not an ordinary struct - so move to next field
continue
}
if hasFiles {
if ok, err := isFieldMultipartFile(structField.Type()); err != nil {
return err
} else if ok {View on GitHub (pinned to 05489dc173)
Solutions
- Remove the binding tag from the anonymous/embedded field; put tags on the inner struct's own fields instead
- If you need the field bound by a specific name, make it a non-anonymous (named) field and add the tag there
- Implement BindUnmarshaler on the embedded type and bind it via a named field with an explicit tag
Example fix
// before
type Req struct {
Pagination `query:"page"`
}
// after
type Req struct {
Pagination
} Defensive patterns
Strategy: validation
Validate before calling
// Scan struct fields at startup for tags on anonymous fields
func checkNoTagsOnAnonymous(rt reflect.Type) error {
for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i)
if f.Anonymous && hasBindTag(f.Tag) {
return fmt.Errorf("anonymous field %s must not have bind tags", f.Name)
}
}
return nil
}
func hasBindTag(t reflect.StructTag) bool {
return t.Get("param") != "" || t.Get("query") != "" || t.Get("form") != "" || t.Get("header") != ""
} Try / catch
if err := c.Bind(&req); err != nil {
if strings.Contains(err.Error(), "anonymous struct field") {
// fix struct definition: remove tag from embedded field
log.Fatal("remove bind tag from embedded field in ", reflect.TypeOf(req).Name())
}
return err
} Prevention
- Never put param/query/form/header tags on the embedding (anonymous) line in a struct
- Put binding tags on the fields inside the embedded struct instead
- Code-review struct definitions that use embedded types for binding DTOs
When it happens
Trigger: Defining a struct with an embedded struct field that has a `query:"..."`, `param:"..."`, `form:"..."`, or `header:"..."` tag, then calling c.Bind / BindQueryParams / BindPathValues / BindHeaders with that struct.
Common situations: Embedding a shared base struct (e.g. Pagination) and accidentally adding a query/form tag to the embedding line. OpenAPI/Swagger code generators that attach tags to promoted embedded fields.
Related errors
- binding element must be a struct
- unknown type
- binding to multipart.FileHeader struct is not supported, use
- %s: %w
- failed to parse form value, key: %s, err: %w
AI-assisted analysis of labstack/echo@05489dc173 (2026-08-04).
Data as JSON: /data/errors/bb3c3100602f0031.json.
Report an issue: GitHub.