ent/ent · error

sql/scan: unsupported type ([]%s)

Error message

sql/scan: unsupported type ([]%s)

What it means

scanType only supports element types that are directly scannable (primitives, string, []byte, sql.Scanner implementations), pointers to those, or structs. Any other slice element kind (map, chan, func, slice, interface with methods, invalid) is rejected with this message naming the kind.

Source

Thrown at dialect/sql/scan.go:159

	return values
}

// scanType returns rowScan for the given reflect.Type.
func scanType(typ reflect.Type, columns []string) (*rowScan, error) {
	switch k := typ.Kind(); {
	case assignable(typ):
		return &rowScan{
			columns: []reflect.Type{typ},
			value: func(v ...any) (reflect.Value, error) {
				return reflect.Indirect(reflect.ValueOf(v[0])), nil
			},
		}, nil
	case k == reflect.Ptr:
		return scanPtr(typ, columns)
	case k == reflect.Struct:
		return scanStruct(typ, columns)
	default:
		return nil, fmt.Errorf("sql/scan: unsupported type ([]%s)", k)
	}
}

var (
	timeType     = reflect.TypeOf(time.Time{})
	scannerType  = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
	nullJSONType = reflect.TypeOf((*nullJSON)(nil)).Elem()
)

// nullJSON represents a json.RawMessage that may be NULL.
type nullJSON json.RawMessage

// Scan implements the sql.Scanner interface.
func (j *nullJSON) Scan(v interface{}) error {
	if v == nil {
		return nil
	}
	*j = v.([]byte)

View on GitHub (pinned to 69d5d4deb1)

Solutions

  1. Scan into a slice of structs (or a sql.Scanner-implementing type) whose fields match the selected columns
  2. Implement the sql.Scanner interface on the element type
  3. If dynamic columns are needed, scan into []any / use rows.Scan manually instead of ScanSlice

Example fix

// before
var out []map[string]any
sql.ScanSlice(rows, &out) // unsupported type ([]map)
// after
type Row struct{ ID int; Name string }
var out []Row
sql.ScanSlice(rows, &out)
Defensive patterns

Strategy: type-guard

Validate before calling

func scannableElem(t reflect.Type) bool {
	switch t.Kind() {
	case reflect.Struct, reflect.Ptr:
		return true
	case reflect.String, reflect.Bool,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64:
		return true
	}
	return reflect.PointerTo(t) != nil && t.Implements(scannerType)
}

Type guard

func isScannableSlice(v any) bool {
	rv := reflect.ValueOf(v)
	return rv.Kind() == reflect.Ptr && rv.Elem().Kind() == reflect.Slice &&
		(rv.Elem().Type().Elem().Kind() == reflect.Struct ||
			rv.Elem().Type().Elem().Implements(reflect.TypeOf((*sql.Scanner)(nil)).Elem()))
}

Prevention

When it happens

Trigger: Calling ScanSlice with a slice whose element kind is not scannable — e.g. *[]map[string]any, []chan int, [][]int, []func(), or a custom struct-like type that neither implements sql.Scanner nor is a struct.

Common situations: Trying to scan raw rows into []map[string]any (a common expectation from other ORMs); passing a slice of slices for multi-column results; typos where the destination should have been a struct.

Related errors


AI-assisted analysis of ent/ent@69d5d4deb1 (2026-09-03). Data as JSON: /api/errors/43a16de326a49979. Report an issue: GitHub.