siyuan-note/siyuan · error

js value cannot be exported to a valid Go value

Error message

js value cannot be exported to a valid Go value

What it means

jsValueToBytes in the plugin sandbox converts a JavaScript value returned by a plugin handler into raw Go bytes. After checking all known JS types (e.g. ArrayBuffer, typed arrays, strings via buffer), none matched the runtime type of the value, so the bridge gives up rather than guessing a serialization. It signals a type contract violation: the plugin returned a JS value the Go side cannot represent as bytes.

Source

Thrown at kernel/plugin/sandbox.go:489

// 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 {
		err = fmt.Errorf("globalThis.siyuan.server[%s][%s] is not an object", scope, requestType)
		return
	}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Fix the plugin handler to return an ArrayBuffer, a typed array (Uint8Array etc.), or a string as its response body.
  2. If returning a Promise, await it inside the handler so the exported value is the resolved type.
  3. Serialize the object in JS first, e.g. `return JSON.stringify(obj)` or `new TextEncoder().encode(JSON.stringify(obj))`.
  4. Check kernel/plugin/sandbox.go jsValueToBytes for the list of supported types and add a conversion case if a new JS type must be supported (kernel-side change).

Example fix

// before (plugin JS)
handler: (req) => ({ ok: true, data: "..." });
// after
handler: (req) => JSON.stringify({ ok: true, data: "..." });
Defensive patterns

Strategy: type-guard

Validate before calling

function isExportableBody(v) {
  return v instanceof ArrayBuffer || ArrayBuffer.isView(v) || typeof v === "string";
}
const body = await handler(req);
if (!isExportableBody(body)) throw new TypeError("handler must return ArrayBuffer, typed array, or string");

Type guard

function isExportableBody(v) {
  return v instanceof ArrayBuffer || ArrayBuffer.isView(v) || typeof v === "string";
}

Prevention

When it happens

Trigger: A plugin request handler registered via globalThis.siyuan.server[scope][requestType].handler resolves/returns a value that is not an ArrayBuffer, typed array, or string (e.g. a plain object, undefined, null, a Promise resolving to an object, or a number), and the Go sandbox calls jsValueToBytes on it.

Common situations: Plugin authors returning JSON objects instead of binary/string bodies from a fetch-like handler; forgetting `await` so the handler returns a Promise; returning undefined from an early-exit code path; a plugin updated to a new response shape that the kernel version doesn't know.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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