siyuan-note/siyuan · error
path must start with /
Error message
path must start with /
What it means
Thrown by siyuan.client.fetch when the first argument (path) is a string but does not begin with '/'. fetch only accepts a server-relative path because it rewrites it to http://127.0.0.1:<port><path>; absolute or scheme-relative URLs are rejected to keep plugin requests on the local kernel.
Source
Thrown at kernel/plugin/api_client.go:64
client := rt.NewObject()
lo.Must0(client.Set("fetch", rt.ToValue(func(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
promise, resolve, reject := rt.NewPromise()
var argErr error
var path string
method := "GET"
headers := map[string]string{}
var bodyString *string
var bodyBytes *[]byte
if goja.IsString(call.Argument(0)) {
path = call.Argument(0).String()
} else {
argErr = fmt.Errorf("path required")
}
if argErr == nil && !strings.HasPrefix(path, "/") {
argErr = fmt.Errorf("path must start with /")
}
if argErr == nil {
if init := call.Argument(1); isJsValueNotNull(init) {
if initObj := init.ToObject(rt); initObj != nil {
if m := initObj.Get("method"); goja.IsString(m) {
method = m.String()
}
if h := initObj.Get("headers"); isJsValueNotNull(h) {
if exportErr := rt.ExportTo(h, &headers); exportErr != nil {
argErr = fmt.Errorf("failed to export headers: %w", exportErr)
}
}
if argErr == nil {
if b := initObj.Get("body"); isJsValueNotNull(b) {
if goja.IsString(b) {
bodyString = new(b.String())View on GitHub (pinned to 251596fc0d)
Solutions
- Prefix the path with '/' so it is server-relative, e.g. '/api/query/similarBlocks'.
- Normalize once: const p = path.startsWith('/') ? path : '/' + path.
- Do not pass a full URL; fetch only targets the local kernel. Strip the origin/host before calling.
Example fix
// before
await siyuan.client.fetch('api/query/similarBlocks', { method: 'POST', body: '{}' });
// after
await siyuan.client.fetch('/api/query/similarBlocks', { method: 'POST', body: '{}' }); Defensive patterns
Strategy: validation
Validate before calling
function safePath(p) {
if (typeof p !== 'string') throw new TypeError('path must be a string');
if (!p.startsWith('/')) throw new Error("path must start with /");
return p;
}
await siyuan.client.fetch(safePath(path)); Type guard
const isServerPath = (v) => typeof v === 'string' && v.startsWith('/'); Prevention
- Always write kernel paths as '/api/...'.
- Centralize endpoint constants so paths are never assembled ad hoc.
- Remember siyuan.client.fetch is not the browser fetch — no origin allowed.
When it happens
Trigger: Calling fetch('api/foo') (missing leading slash), fetch('http://host/api/foo') (absolute URL), or fetch('//host/path') (protocol-relative). Any string passed as arg 0 that lacks a '/' prefix fails the strings.HasPrefix check at api_client.go:63.
Common situations: Plugin author copies a URL from browser devtools (which may keep a relative form), builds the path from a variable that was trimmed, or mistakes siyuan.client.fetch for the browser fetch (which accepts full URLs).
Related errors
- custom emoji name must not be empty
- invalid path
- Untitled
- Cannot create a file starting with .
- Duplicated filename
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/886561280f49dc3f.
Report an issue: GitHub.