siyuan-note/siyuan · error
path must start with /
Error message
path must start with /
What it means
The fetch bridge validates that the request path starts with "/" — only kernel-relative API paths are allowed, not absolute URLs. A path without the leading slash is rejected so plugins cannot point the client at arbitrary hosts or malformed endpoints.
Solutions
- Prefix the path with "/": fetch("/api/...")
- Strip the origin from full URLs: new URL(full).pathname
- Do not use absolute URLs — the client always targets the local kernel
- Add a JS-side guard: if (!p.startsWith("/")) p = "/" + p
Example fix
// before
await siyuan.client.fetch("http://127.0.0.1:6806/api/notebook/lsNotebooks")
// after
await siyuan.client.fetch("/api/notebook/lsNotebooks") Defensive patterns
Strategy: validation
Validate before calling
function toKernelPath(p) {
if (typeof p !== "string") throw new TypeError("path must be a string");
if (/^https?:\/\//.test(p)) return new URL(p).pathname;
return p.startsWith("/") ? p : "/" + p;
} Type guard
const isRelativeApiPath = (v) => typeof v === "string" && v.startsWith("/"); Try / catch
try {
const res = await siyuan.client.fetch(path, opts);
} catch (e) {
if (String(e).includes("path must start with /")) {
console.error("Use a kernel-relative path like /api/...", path);
}
} Prevention
- Strip origins from copied curl/doc URLs with new URL(u).pathname
- Never pass absolute URLs to siyuan.client.fetch
- Centralize all kernel calls in one helper that normalizes paths
When it happens
Trigger: Calling siyuan.client.fetch("api/notebook/lsNotebooks") (missing leading slash), or passing a full URL like "http://127.0.0.1:6806/api/..." or "https://...".
Common situations: Copying absolute URLs from API documentation or curl examples directly into the plugin client; building paths from config that dropped the leading slash.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- failed to export headers
- path required
- panic during siyuan.client.fetch
- Access to encrypted notebook data is not supported via this…
- AI editor action must not be empty
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/886561280f49dc3f.
Report an issue: GitHub.
Appendix: 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 9f775e8a12)