labstack/echo · error

binding element must be a struct

Error message

binding element must be a struct

What it means

Returned by bindData when the destination passed to form binding is not a struct and not one of the supported map types. Echo's form/path/query/header binder uses reflection to iterate struct fields with binding tags; a non-struct destination (e.g. a bare *string, *int, or *[]string) has no fields to bind into, so the binder refuses it. This only fires for the 'form' source; param/query/header sources silently return nil for incompatible types assuming the data lives in the body.

Source

Thrown at bind.go:253

				val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
			} else if isElemInterface {
				// To maintain backward compatibility, we always bind to the first string value
				// and not the slice of strings when dealing with map[string]any{}
				val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
			} else {
				val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v))
			}
		}
		return nil
	}

	// !struct
	if typ.Kind() != reflect.Struct {
		if tag == "param" || tag == "query" || tag == "header" {
			// incompatible type, data is probably to be found in the body
			return nil
		}
		return errors.New("binding element must be a struct")
	}

	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

View on GitHub (pinned to 05489dc173)

Solutions

  1. Wrap the primitive in a struct with a form tag, e.g. type Req struct{ Name string `form:"name"` }, and bind to &Req{}
  2. If you only need a single value use c.FormValue("name") or c.QueryParam("name") instead of Bind
  3. JSON/XML bodies do not go through bindData, so c.Bind with application/json supports any JSON-decodable type

Example fix

// before
var name string
c.Bind(&name)

// after
var req struct {
    Name string `form:"name"`
}
c.Bind(&req)
Defensive patterns

Strategy: validation

Validate before calling

// Before calling c.Bind, verify the destination is a struct pointer
func isStructPtr(v any) bool {
    t := reflect.TypeOf(v)
    return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
}
if !isStructPtr(target) {
    return errors.New("bind target must be a pointer to struct")
}

Type guard

func isStructPtr(v any) bool {
    t := reflect.TypeOf(v)
    return t != nil && t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
}

Try / catch

if err := c.Bind(&req); err != nil {
    if strings.Contains(err.Error(), "binding element must be a struct") {
        return echo.NewHTTPError(http.StatusBadRequest, "invalid request structure")
    }
    return err
}

Prevention

When it happens

Trigger: Calling c.Bind(&someString) or BindBody where the target is *string/*int/*[]string and the Content-Type is application/x-www-form-urlencoded or multipart/form-data, so bindData is invoked with tag "form" and a non-struct destination.

Common situations: Developer tries to bind a single form field value directly to a primitive instead of wrapping it in a struct. Migrating a JSON handler (which uses the JSON serializer) to form handling and forgetting to change the destination type.

Related errors


AI-assisted analysis of labstack/echo@05489dc173 (2026-08-04). Data as JSON: /data/errors/eb29ca6c7f1c4c0d.json. Report an issue: GitHub.