siyuan-note/siyuan · error

handler did not return an object

Error message

handler did not return an object

What it means

In handleHttpRequest, after the JS handler returns, the kernel converts result.Value to an object; if ToObject yields nil (the handler returned undefined/null or a primitive that cannot become an object in a meaningful way), the kernel reports `handler did not return an object` as the handleResult error. HTTP handling expects a response-shaped object with fields like status/body.

Solutions

  1. Make the handler always return a response object on every code path, e.g. `{ status: 200, body: ... }`
  2. Ensure the Promise passed to invokeFunction resolves to the response, not to a side-effect-only call
  3. Add a JS-side guard: if the computed response is falsy, return a default 500 response object
  4. Check early-return branches in the handler for missing return values

Example fix

// before
async handle(req) {
  if (!req.path) return            // falls through -> undefined
  return { status: 200, body: 'ok' }
}
// after
async handle(req) {
  if (!req.path) return { status: 400, body: 'missing path' }
  return { status: 200, body: 'ok' }
}
Defensive patterns

Strategy: validation

Validate before calling

// plugin JS: validate the response before returning it
function makeResponse(status, body) {
  if (typeof status !== 'number') status = 500
  if (body === undefined || body === null) body = ''
  return { status: status, body: body }
}

Type guard

// plugin JS
function isResponseObject(v) {
  return v !== null && typeof v === 'object' && typeof v.status === 'number'
}

Try / catch

// kernel caller side
res := p.handleHttpRequest(req)
if res.Error != nil && res.Error.Error() == "handler did not return an object" {
  http.Error(w, "plugin handler returned no response", http.StatusBadGateway)
}

Prevention

When it happens

Trigger: A plugin HTTP-request handler that returns nothing (falls off the end), returns undefined/null explicitly, or returns a Promise resolved to undefined instead of a response object.

Common situations: Handler with early-return paths that skip building the response; awaiting a helper that returns undefined on error; plugin returning the result of a void function instead of a `{status, body}` object.

Related errors


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

Appendix: source

Thrown at kernel/plugin/plugin.go:928

			err = getHandlerErr
			return
		}

		jsRequest, convertErr := requestGoToJs(p, rt, request)
		if convertErr != nil {
			err = convertErr
			return
		}

		invokeFunction(func(rt *goja.Runtime, result *CallResult) {
			if result.Error != nil {
				done <- &handleResult{Error: result.Error}
				return
			}

			responseObj := result.Value.ToObject(rt)
			if responseObj == nil {
				done <- &handleResult{Error: fmt.Errorf("handler did not return an object")}
				return
			}

			// convert response.body?.raw?.data from (string | Buffer | ArrayBuffer) to []byte
			var raw *[]byte
			if bodyValue := responseObj.Get("body"); isJsValueNotNull(bodyValue) {
				// response.body
				if bodyObj := bodyValue.ToObject(rt); bodyObj != nil {
					if rawValue := bodyObj.Get("raw"); isJsValueNotNull(rawValue) {
						// response.body.raw
						if rawObj := rawValue.ToObject(rt); rawObj != nil {
							if dataValue := rawObj.Get("data"); isJsValueNotNull(dataValue) {
								// response.body.raw.data
								dataBytes, convertErr := jsValueToBytes(rt, dataValue)
								if convertErr == nil {
									raw = &dataBytes
									rawObj.Set("data", goja.Null())
								}

View on GitHub (pinned to 8641553a1f)