go-sql-driver/mysql · error
cannot convert type: %T
Error message
cannot convert type: %T
What it means
While encoding prepared-statement arguments for the MySQL binary protocol, writeExecutePacket handles only nil/int64/uint64/float64/bool/[]byte/string/time.Time/json.RawMessage. Any other dynamic type falls through to this default. Normally ConvertValue normalizes Go types first, so this fires when a value bypasses that normalization (custom CheckNamedValue, direct driver use, or a non-canonical driver.Value).
Solutions
- Ensure custom Valuers return a standard driver.Value (int64, string, []byte, time.Time, etc.).
- Implement driver.Valuer to convert your type into a supported primitive.
- Convert the value to a supported Go type before passing it to Exec.
Example fix
// before
type Money struct{ Cents int64 }
// Money not a Valuer -> "cannot convert type: *Money"
stmt.Exec(Money{100})
// after
func (m Money) Value() (driver.Value, error) { return m.Cents, nil }
stmt.Exec(Money{100}) Defensive patterns
Strategy: validation
Validate before calling
// Confirm each arg is a type the wire encoder accepts before Exec.
func wireEncodable(v any) bool {
switch v.(type) {
case nil, int64, uint64, float64, bool, []byte, string, time.Time:
return true
}
if _, ok := v.(driver.Valuer); ok { return true }
return false
} Type guard
func isDriverValue(v any) bool {
if v == nil { return true }
switch v.(type) {
case int64, float64, bool, []byte, string, time.Time:
return true
}
return false
} Try / catch
// Returned from Exec/Query; indicates a non-encodable argument type.
if _, err := stmt.Exec(args...); err != nil {
if strings.Contains(err.Error(), "cannot convert type") {
// fix the offending arg's type
}
} Prevention
- Make domain types implement driver.Valuer returning a primitive.
- Avoid passing structs/maps to prepared statements directly.
- Validate custom types' Value() return type in unit tests.
When it happens
Trigger: A custom driver.Valuer or driver.NamedValueChecker returning an unusual type; calling the driver directly with a struct/map/raw int; a value that survives database/sql's conversion as a non-standard type.
Common situations: Custom wrapper types that aren't true driver.Value types; passing a domain struct instead of one of its fields.
Related errors
- non-Value type %T returned from Value
- unsupported type %T, a
- unsupported type %T, a slice of
- argument count mismatch
- can't convert %T to time.Time
AI-assisted analysis of go-sql-driver/mysql@03d76c7e07 (2026-08-07).
Data as JSON: /api/errors/933fd9db908a34ea.
Report an issue: GitHub.
Appendix: source
Thrown at packets.go:1223
var a [64]byte
var b = a[:0]
if v.IsZero() {
b = append(b, "0000-00-00"...)
} else {
b, err = appendDateTime(b, v.In(mc.cfg.Loc), mc.cfg.timeTruncate)
if err != nil {
return err
}
}
paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(b)),
)
paramValues = append(paramValues, b...)
default:
return fmt.Errorf("cannot convert type: %T", arg)
}
}
// Check if param values exceeded the available buffer
// In that case we must build the data packet with the new values buffer
if valuesCap != cap(paramValues) {
data = append(data[:pos], paramValues...)
mc.buf.store(data) // allow this buffer to be reused
}
pos += len(paramValues)
data = data[:pos]
}
err = mc.writePacket(data)
mc.syncSequence()
return err
}View on GitHub (pinned to 03d76c7e07)