[Bug] createDocWithMd creates unintended subdirectories when document titles contain "/"

Hello everyone,

I encountered an unexpected behavior while working with the SiYuan Kernel API, specifically** /api/filetree/createDocWithMd, when creating documents whose titles contain **/.

The API appears to interpret** **/ inside the document title as a path separator, resulting in unintended directory creation rather than treating it as part of the document name.

Environment

  • SiYuan / Sedge: 3.8.3+
  • OS: macOS ARM64 / Cross-platform
  • Interface: Kernel HTTP API —** **/api/filetree/createDocWithMd

Issue

For example, if I attempt to create a document with the path:

/Documentation/Overview (kernel/api)

I would expect a single document named:

Overview (kernel/api)

However, the actual result is:

Documentation/
└── Overview (kernel/
    └── api).sy

Similarly, creating:

/Notes/Programming in C/C++

results in:

Notes/
└── Programming in C/
    └── C++.sy

This happens silently, which can be particularly problematic for import scripts and plugins that generate documents programmatically.

Steps to reproduce

Send a request to:

POST /api/filetree/createDocWithMd

For example:

curl -X POST http://127.0.0.1:6806/api/filetree/createDocWithMd \
  -H "Authorization: Token <YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "notebook": "<YOUR_NOTEBOOK_ID>",
    "path": "/Notes/Programming in C/C++",
    "markdown": "# Introduction\n\nContent goes here."
  }'

The resulting filesystem structure contains a** Programming in C directory with C++ as a child document instead of creating a single document named **Programming in C/C++.

Possible root cause

Looking at** **kernel/api/filetree.go, the relevant logic appears to be:

baseName := path.Base(hPath)
dir := path.Dir(hPath)

r, _ := regexp.Compile("\r\n|\r|\n|\u2028|\u2029|\t|/")
baseName = r.ReplaceAllString(baseName, "")

if 512 < utf8.RuneCountInString(baseName) {
	baseName = gulu.Str.SubStr(baseName, 512)
}

hPath = path.Join(dir, baseName)

The potential issue is that** path.Dir() and path.Base() are evaluated **before / is sanitized.

Since Go's** path package treats **/ as a path separator, a slash contained in what the caller intended to be the document title is already interpreted as directory hierarchy before the sanitization step occurs.

Expected behavior

I think the API should either:

  1. Sanitize the document title before resolving the directory structure,** **or
  2. Explicitly reject** / in document titles with a validation error, making it clear that **/ is reserved for path hierarchy.

Possible approaches could be:

  • Separate the folder path and document name into distinct API parameters.
  • Sanitize the target title before calling** path.Dir() / **path.Base().
  • Return a validation error such as:
{
  "code": -1,
  "msg": "Document title cannot contain slashes"
}

The main concern is the** **silent creation of unintended directories, especially when this API is used by automated importers, plugins, or other tooling.

Has anyone else encountered this behavior when working with import scripts or plugins?

I’d also appreciate the maintainers' thoughts on whether** / is intentionally supported in document titles and, if so, how it is expected to be handled by **createDocWithMd.

Thanks.

    Welcome to here!

    Here we can learn from each other how to use SiYuan, give feedback and suggestions, and build SiYuan together.

    Signup About
    Please input reply content ...
    • 88250

      Thanks for the detailed report. The path parameter of createDocWithMd represents a hierarchical document path, with / separating levels, as described in the API documentation. Therefore, /Notes/Programming in C/C++ is interpreted as three document levels.

      This behavior is intentional rather than a sanitization-order bug. With a single path string, the API cannot distinguish a slash intended as part of a title from a hierarchy separator. Document title normalization also currently removes /.

      For import scripts, please sanitize each title before joining it into the path—for example, replace / with (a full-width slash).

      We recognize that this can be confusing. The documentation could make this limitation clearer, and separating the parent location and title is a reasonable suggestion for a future API improvement.