{"record":{"id":"dd8da81a7bbfe366","repo":"siyuan-note/siyuan","slug":"siyuan-server-s-s-handler-is-not-a-function","errorCode":null,"errorMessage":"siyuan.server[%s][%s].handler is not a function","messagePattern":"siyuan\\.server\\[(.+?)\\]\\[(.+?)\\]\\.handler is not a function","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/sandbox.go","lineNumber":517,"sourceCode":"\t\treturn\n\t}\n\n\thandlerObj = handlerObjValue.ToObject(rt)\n\tif handlerObj == nil {\n\t\terr = fmt.Errorf(\"globalThis.siyuan.server[%s][%s] is not an object\", scope, requestType)\n\t\treturn\n\t}\n\n\t// Get handler: siyuan.server[scope][requestType].handler\n\thandlerValue := handlerObj.Get(\"handler\")\n\tif !isJsValueNotNull(handlerValue) {\n\t\terr = fmt.Errorf(\"siyuan.server[%s][%s].handler is not set\", scope, requestType)\n\t\treturn\n\t}\n\n\thandler, ok := goja.AssertFunction(handlerValue)\n\tif !ok {\n\t\terr = fmt.Errorf(\"siyuan.server[%s][%s].handler is not a function\", scope, requestType)\n\t\treturn\n\t}\n\n\treturn\n}\n\n// requestGoToJs converts a Go Request to a JavaScript value.\nfunc requestGoToJs(p *KernelPlugin, rt *goja.Runtime, request *Request) (jsRequest goja.Value, err error) {\n\t// convert body raw data to js object\n\tif data, ok := request.Request.Body.Data.(*[]byte); ok && data != nil {\n\t\trequest.Request.Body.Data, err = NewDataObject(p, rt, *data)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\t// convert body form files data to js object\n\tif request.Request.Body.Form != nil {","sourceCodeStart":499,"sourceCodeEnd":535,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/sandbox.go#L499-L535","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nsiyuan.server['plugin']['request'] = { handler: { method: 'POST' } };\n// after\nsiyuan.server['plugin']['request'] = { handler: async (req) => ({ code: 0 }) };","handlingStrategy":"type-guard","validationCode":"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}`);","typeGuard":"function isCallable(v) { return typeof v === 'function'; }","tryCatchPattern":"try { await request(scope, type); } catch (e) { if (String(e).includes('.handler is not a function')) { reRegisterPluginHandlers(); } else { throw e; } }","preventionTips":["Assign actual functions (async (req) => ...) to handler","Assert typeof handler === 'function' immediately after registration","Avoid key collisions where other code overwrites the handler property"],"tags":["goja","plugin","type-error","handler"],"backgroundTag":"type-mismatch","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}