siyuan-note/siyuan · error
siyuan.server[ ][ ].handler is not a function
Error message
siyuan.server[%s][%s].handler is not a function
What it means
getRequestHandler finds a non-null 'handler' property but goja.AssertFunction fails, meaning the property holds a value that is not callable (plain object, string, number, etc.). The sandbox requires handlers to be JS functions.
Solutions
- Ensure handler is a function: obj.handler = async (request) => ({...})
- Check typeof globalThis.siyuan.server[scope][requestType].handler === 'function' at registration time
- Look for other plugin code overwriting the same scope/requestType key
Example fix
// before
siyuan.server['plugin']['request'] = { handler: { method: 'POST' } };
// after
siyuan.server['plugin']['request'] = { handler: async (req) => ({ code: 0 }) }; Defensive patterns
Strategy: type-guard
Validate before calling
const h = globalThis.siyuan?.server?.[scope]?.[requestType]?.handler; if (typeof h !== 'function') throw new Error(`handler for ${scope}/${requestType} must be a function, got ${typeof h}`); Type guard
function isCallable(v) { return typeof v === 'function'; } Try / catch
try { await request(scope, type); } catch (e) { if (String(e).includes('.handler is not a function')) { reRegisterPluginHandlers(); } else { throw e; } } Prevention
- Assign actual functions (async (req) => ...) to handler
- Assert typeof handler === 'function' immediately after registration
- Avoid key collisions where other code overwrites the handler property
When it happens
Trigger: Assigning something non-callable to siyuan.server[scope][requestType].handler, e.g. { handler: { ... } }, { handler: 'string' }, or a value that got overwritten by other plugin code.
Common situations: Copy-paste mistakes in plugin registration, assigning a config object instead of the function, or a name collision where another plugin/property replaced the handler.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- unsupported data type: %T
- failed to serialize config.
- failed to serialize inputSchema
- globalThis.siyuan.plugin.lifecycle is not an object
- globalThis.siyuan.plugin.lifecycle not found
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/dd8da81a7bbfe366.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/plugin/sandbox.go:517
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 {
return
}
}
// convert body form files data to js object
if request.Request.Body.Form != nil {View on GitHub (pinned to 9f775e8a12)