siyuan-note/siyuan · error

siyuan.server[%s][%s].handler is not set

Error message

siyuan.server[%s][%s].handler is not set

What it means

Thrown by getRequestHandler when globalThis.siyuan.server[scope][requestType] exists as an object but has no `handler` property that isJsValueNotNull — the registration object is present but the handler callable is missing. The plugin registered a scope/requestType entry without assigning .handler.

Source

Thrown at kernel/plugin/sandbox.go:511

// 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
	}

	// Get handler: siyuan.server[scope][requestType].handler
	handlerValue := handlerObj.Get("handler")
	if !isJsValueNotNull(handlerValue) {
		err = fmt.Errorf("siyuan.server[%s][%s].handler is not set", scope, requestType)
		return
	}

	handler, ok := goja.AssertFunction(handlerValue)
	if !ok {
		err = fmt.Errorf("siyuan.server[%s][%s].handler is not a function", scope, requestType)
		return
	}

	return
}

// requestGoToJs converts a Go Request to a JavaScript value.
func requestGoToJs(p *KernelPlugin, rt *goja.Runtime, request *Request) (jsRequest goja.Value, err error) {
	// convert body raw data to js object
	if data, ok := request.Request.Body.Data.(*[]byte); ok && data != nil {
		request.Request.Body.Data, err = NewDataObject(p, rt, *data)
		if err != nil {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Add a callable `handler` to the registration object: { handler: fn }.
  2. Confirm handler is a function and not undefined/null (isJsValueNotNull must pass).
  3. Match the scope/requestType string keys to the kernel's AccessScope/RequestType constants.

Example fix

// before
globalThis.siyuan.server.network.beforeRequest = { priority: 10 }
// after
globalThis.siyuan.server.network.beforeRequest = { priority: 10, handler: (req) => { /*...*/ } }
Defensive patterns

Strategy: validation

Validate before calling

// Plugin side: assert handler present before registration completes.
function registerHandler(scope: string, rt: string, fn: Function): void {
  const entry = { handler: fn }
  if (typeof entry.handler !== 'function') throw new Error('handler missing')
  globalThis.siyuan.server[scope][rt] = entry
}

Type guard

function hasHandler(v: unknown): v is { handler: Function } {
  return !!v && typeof v === 'object' && typeof (v as any).handler === 'function'
}

Prevention

When it happens

Trigger: Plugin sets globalThis.siyuan.server.network.beforeRequest = { priority: 1 } (or {}) but omits the `handler` field; or sets handler to undefined/null explicitly.

Common situations: Plugin author added metadata fields (priority, label) but forgot handler; partial/typo registration; copied a sample and dropped the handler line.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/ad1826c7c9be1b81. Report an issue: GitHub.