siyuan-note/siyuan · error

unsupported data type: %T

Error message

unsupported data type: %T

What it means

jsValueToBytes converts a JS value to a Go byte slice, accepting string, []byte (Buffer), goja.ArrayBuffer, and buffer.Buffer. Any other exported Go type falls into the default branch and raises 'unsupported data type: %T' naming the actual type. It is the kernel's strictness when plugin APIs expect binary or text data.

Source

Thrown at kernel/plugin/sandbox.go:485

// isJsValueNotNull checks if a goja.Value is not nil, undefined or null.
func isJsValueNotNull(jsValue goja.Value) bool {
	return isJsValueNotUndefined(jsValue) && !goja.IsNull(jsValue)
}

// jsValueToBytes attempts to convert a goja.Value to a byte slice, supporting string, Buffer, ArrayBuffer, etc.
func jsValueToBytes(rt *goja.Runtime, value goja.Value) (data []byte, err error) {
	if goValue := value.Export(); goValue != nil {
		switch d := goValue.(type) {
		case string: // string
			data = []byte(d)
		case []byte: // Buffer
			data = d
		case goja.ArrayBuffer: // ArrayBuffer
			data = d.Bytes()
		case buffer.Buffer: // ?
			data = buffer.Bytes(rt, value)
		default:
			err = fmt.Errorf("unsupported data type: %T", goValue)
		}
		return
	}
	err = fmt.Errorf("js value cannot be exported to a valid Go value")
	return
}

// getRequestHandler retrieves the handler function and its containing object for a given scope and request type from the plugin's JS context.
func getRequestHandler(rt *goja.Runtime, scope AccessScope, requestType RequestType) (handler goja.Callable, handlerObj *goja.Object, err error) {
	// Get handler object: siyuan.server[scope][requestType]
	handlerObjValue, getObjErr := getJsContextValue(rt, []any{"siyuan", "server", string(scope), string(requestType)})
	if getObjErr != nil {
		err = getObjErr
		return
	}

	handlerObj = handlerObjValue.ToObject(rt)
	if handlerObj == nil {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Convert the value before calling the API: use JSON.stringify for objects, String(n) for numbers
  2. For binary data pass an ArrayBuffer or Uint8Array's .buffer; for text pass a string
  3. For DataView, pass dataView.buffer or new Uint8Array(dataView.buffer, dataView.byteOffset, dataView.byteLength)

Example fix

// before
pluginApi.writeData({ raw: bytes }); // object
// after
pluginApi.writeData(new Uint8Array(bytes).buffer);
Defensive patterns

Strategy: validation

Validate before calling

function assertBytesLike(v) { if (typeof v !== 'string' && !(v instanceof ArrayBuffer) && !ArrayBuffer.isView(v)) { throw new TypeError('expected string, Buffer or ArrayBuffer, got ' + typeof v); } }

Type guard

const isBytesLike = (v) => typeof v === 'string' || v instanceof ArrayBuffer || ArrayBuffer.isView(v);

Try / catch

try { api.writeData(payload); } catch (e) { if (String(e).includes('unsupported data type')) { api.writeData(String(payload)); } }

Prevention

When it happens

Trigger: A plugin passes a non-binary JS value (plain object, number, boolean, array, function) to a kernel API whose data parameter is converted with jsValueToBytes — e.g. file/network APIs expecting string, Buffer, or ArrayBuffer.

Common situations: Passing a JSON object where a string/Buffer was expected; passing null/undefined-adjacent wrappers; passing a DataView (exports as plain object) instead of its .buffer ArrayBuffer; passing a number where a string was intended.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/13aa52ee65cc020b. Report an issue: GitHub.