grafana/k6 · error

invalid type %T, expected string, []byte or ArrayBuffer

Error message

invalid type %T, expected string, []byte or ArrayBuffer

What it means

webcrypto's ToBytes helper coerces raw key material and payload arguments. k6 only accepts strings, Go []byte values, and sobek.ArrayBuffer objects; anything else (number, null, plain object, DataView, BigInt, function) reaches the default branch and raises this TypeError-style error naming the offending Go type.

Source

Thrown at internal/js/modules/k6/webcrypto/types.go:35

// byteLength is a type alias for the length of a byte slice.
type byteLength int

// asBitLength returns the length of the byte slice in bits.
func (b byteLength) asBitLength() bitLength {
	return bitLength(b) * 8
}

// ToBytes tries to return a byte slice from compatible types.
func ToBytes(data any) ([]byte, error) {
	switch dt := data.(type) {
	case []byte:
		return dt, nil
	case string:
		return []byte(dt), nil
	case sobek.ArrayBuffer:
		return dt.Bytes(), nil
	default:
		return nil, fmt.Errorf("invalid type %T, expected string, []byte or ArrayBuffer", data)
	}
}

View on GitHub (pinned to 93accf6570)

Solutions

  1. Convert the value to a string or ArrayBuffer before the call: String(x), x.toString(), or buffer.slice(0) for ArrayBuffers
  2. Replace DataView inputs with their underlying ArrayBuffer: dv.buffer
  3. For JSON data, JSON.stringify the object instead of passing it directly
  4. If the value may legitimately be absent, guard with a default: data ?? ''

Example fix

// before
const ct = await crypto.subtle.encrypt(alg, key, jsonDataObj); // object -> invalid type map[string]interface {}

// after
const ct = await crypto.subtle.encrypt(alg, key, JSON.stringify(jsonDataObj));
// or: new TextEncoder().encode(JSON.stringify(jsonDataObj)) for explicit bytes
Defensive patterns

Strategy: type-guard

Validate before calling

function toCryptoBytes(v) {
  if (typeof v === 'string') return v;
  if (v instanceof ArrayBuffer) return v;
  if (ArrayBuffer.isView(v)) return v.buffer; // DataView/TypedArray -> its ArrayBuffer
  throw new Error(`unsupported crypto input: ${typeof v}`);
}
await crypto.subtle.encrypt(alg, key, toCryptoBytes(data));

Type guard

const isCryptoBytes = v => typeof v === 'string' || v instanceof ArrayBuffer;
// TypedArrays whose export is []byte are fine too; DataView must be converted:
const okInput = v => typeof v === 'string' || v instanceof ArrayBuffer || (ArrayBuffer.isView(v) && v.constructor.name === 'Uint8Array');

Try / catch

try { await crypto.subtle.encrypt(alg, key, data); }
catch (e) { if (/invalid type/.test(e.message)) throw new TypeError(`encrypt payload must be string/ArrayBuffer, got ${typeof data}`); throw e; }

Prevention

When it happens

Trigger: Passing a non-buffer first argument to crypto.subtle.encrypt/decrypt/sign/verify/deriveBits/deriveKey or importKey('raw'|'jwk', ...), e.g. importKey('raw', 42, ...), encrypt(alg, key, {data:...}), or a DataView/JSON object instead of a string/ArrayBuffer. Note DataView is NOT in the accepted list even though ArrayBuffer is.

Common situations: Passing a parsed JSON object where the code meant obj.password (a string); passing a DataView created from a buffer; passing null after a failed upstream fetch/parse; assuming numbers are coerced to strings like in console.log; passing a TypedArray of a wide element type whose export is not []byte.

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/80dd296d830a5957. Report an issue: GitHub.