siyuan-note/siyuan · error
template source must use the .md extension
Error message
template source must use the .md extension
What it means
ManageTemplateFiles enforces that any non-directory template path ends with the .md extension (case-insensitive). Directories (including template packages) are exempt. This keeps the templates folder to markdown files plus package directories and prevents writing or operating on files that could not be used as templates.
Source
Thrown at kernel/model/template_manage.go:257
if err = checkTemplateFilePath(root, request.Path); err != nil {
return nil, err
}
abs := filepath.Join(root.Name(), filepath.FromSlash(request.Path))
filelock.Lock(abs)
defer filelock.Unlock(abs)
info, statErr := root.Stat(request.Path)
if request.Action == "mkdir" {
if err = validateNewTemplateName(request.Path); err != nil {
return nil, err
}
return nil, root.Mkdir(request.Path, 0755)
}
if statErr != nil && !(request.Action == "write" && request.Revision == "" && errors.Is(statErr, os.ErrNotExist)) {
return nil, statErr
}
if info == nil || !info.IsDir() {
if !strings.EqualFold(path.Ext(request.Path), ".md") {
return nil, errors.New("template source must use the .md extension")
}
}
if request.Action == "read" {
if info.IsDir() {
revision, readErr := templateFileRevision(root, request.Path)
return map[string]string{"content": "", "revision": revision}, readErr
}
content, readErr := readTemplateSource(root, request.Path)
return map[string]string{"content": content, "revision": fmt.Sprintf("%x", sha256.Sum256([]byte(content))), "path": filepath.Join(util.DataDir, "templates", filepath.FromSlash(request.Path))}, readErr
}
if info != nil {
revision, revisionErr := templateFileRevision(root, request.Path)
if revisionErr != nil {
return nil, revisionErr
}
if request.Revision == "" || request.Revision != revision {
return nil, errors.New("template changed; reload it before saving, moving or deleting")
}View on GitHub (pinned to 8641553a1f)
Solutions
- Rename the target/path so it ends with .md, e.g. write to "notes.md" instead of "notes.txt"
- For a move, append ".md" to request.Target before calling the API
- If you intended a folder, send a directory path (no extension needed) — directories are exempt from this check
Example fix
// before
{ "action": "move", "path": "a.md", "target": "notes/renamed" }
// after
{ "action": "move", "path": "a.md", "target": "notes/renamed.md" } Defensive patterns
Strategy: validation
Validate before calling
function requireMd(p) {
if (!/\.md$/i.test(p)) throw new Error(`template path must end in .md: ${p}`);
} Try / catch
try {
await manageTemplateFiles({ action: 'write', path: p, ... });
} catch (e) {
if (String(e.message).includes('must use the .md extension')) {
p = p.replace(/\.[^.]*$/, '') + '.md'; // normalize extension and retry
}
} Prevention
- Append .md to every file path you send to the template API; the kernel never adds it for you
- Only directories are exempt — keep folder paths extension-free and file paths .md
- Note .markdown/.mdown are not accepted; only .md (case-insensitive)
When it happens
Trigger: Calling ManageTemplateFiles with any action (write/move/read) where request.Path (or, for move, request.Target on a file) does not end in .md — e.g. writing "notes.txt", moving "a.md" to "b.markdown" or to an extensionless name, or operating on a file whose extension uses unsupported casing is fine but a wrong extension is not.
Common situations: A script generated template files with .txt or .mdown extensions; a move target was constructed without an extension; the client assumed the kernel would append .md automatically — it does not.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- invalid template path component
- custom emoji name must not be empty
- invalid custom emoji name
- path is required
- Conf.Language(37)
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/1e52c4a9345f005b.
Report an issue: GitHub.