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

  1. Rename the target/path so it ends with .md, e.g. write to "notes.md" instead of "notes.txt"
  2. For a move, append ".md" to request.Target before calling the API
  3. 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

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


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/1e52c4a9345f005b. Report an issue: GitHub.