siyuan-note/siyuan · error

Please configure [Settings - Export - Pandoc - Path to…

Error message

Please configure [Settings - Export - Pandoc - Path to Pandoc executable] first

What it means

model.ExportDocx (POST /api/export/exportDocx) shells out to pandoc. It resolves the binary via util.GetPandocRuntime and validates it with util.IsValidPandocBin; if invalid it re-initializes from Conf.Export.PandocBin and re-checks, and if still invalid returns the localized error asking for Settings - Export - Pandoc - Path to Pandoc executable (Conf.Language(115)). No conversion is attempted without a working pandoc.

Solutions

  1. Install pandoc (winget/choco/brew/apt) or download the official release
  2. Set the absolute binary path in Settings - Export - Pandoc - Path to Pandoc executable, or clear it to let the kernel auto-detect from PATH
  3. Verify as the same user that runs the kernel: <path-to-pandoc> --version
  4. On Linux chmod +x the binary; on macOS remove the quarantine attribute (xattr -d com.apple.quarantine <binary>) if Gatekeeper blocks it
  5. Retry the export — the kernel re-inits pandoc from config on each call

Example fix

# before: ExportDocx -> "Please configure [Settings - Export - Pandoc - Path to Pandoc executable] first"

# after: install, configure, retry
brew install pandoc   # or: choco install pandoc / apt install pandoc
# Settings - Export - Pandoc - Path to Pandoc executable: /opt/homebrew/bin/pandoc
# then retry POST /api/export/exportDocx
Defensive patterns

Strategy: validation

Validate before calling

if !util.IsValidPandocBin(util.GetPandocRuntime().BinPath) {
	util.InitPandoc(model.Conf.Export.PandocBin)
	if !util.IsValidPandocBin(util.GetPandocRuntime().BinPath) {
		return errors.New("configure a valid pandoc binary before docx export")
	}
}
fullPath, err := model.ExportDocx(id, savePath, false, false)

Try / catch

if _, err := model.ExportDocx(id, savePath, false, false); err != nil {
	if err.Error() == model.Conf.Language(115) {
		// pandoc missing: prompt to install/configure, then let the user retry
	}
}

Prevention

When it happens

Trigger: POST /api/export/exportDocx when pandoc is not installed, Conf.Export.PandocBin points to a missing or non-executable file, or the binary exists but fails the validity invocation (wrong architecture, blocked by macOS Gatekeeper).

Common situations: Fresh installs without pandoc; Docker/minimal images; portable setups where the configured path went stale after an update; mis-entered paths with quotes or spaces; downloaded binaries lacking the exec bit on Linux.

Related errors


AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18). Data as JSON: /api/errors/7fbe6f3ab6bd153a. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/export.go:1054

		if footnotesDefBlock := tree.Root.ChildByType(ast.NodeFootnotesDefBlock); nil != footnotesDefBlock {
			footnotesDefBlock.Unlink()
		}
		return nil
	}); exportErr != nil {
		logging.LogErrorf("export preview [%s] failed: %s", id, exportErr)
		return
	}
	return
}

func ExportDocx(id, savePath string, removeAssets, merge bool, mergeHeadingOptions ...MergeHeadingOptions) (fullPath string, err error) {
	err = withExportReadLockByBlockID(id, func() error {
		pandocRuntime := util.GetPandocRuntime()
		if !util.IsValidPandocBin(pandocRuntime.BinPath) {
			util.InitPandoc(Conf.Export.PandocBin)
			pandocRuntime = util.GetPandocRuntime()
			if !util.IsValidPandocBin(pandocRuntime.BinPath) {
				return errors.New(Conf.Language(115))
			}
		}

		tmpDir := filepath.Join(util.TempDir, "export", gulu.Rand.String(7))
		if bt := getExportBlockTree(id); bt != nil && IsEncryptedBox(bt.BoxID) {
			exportID, idErr := newManagedEncryptedExportID()
			if idErr != nil {
				return idErr
			}
			tmpDir = filepath.Join(util.TempDir, "export", bt.BoxID, "docx", exportID)
		}
		if mkdirErr := os.MkdirAll(tmpDir, 0755); mkdirErr != nil {
			return mkdirErr
		}
		defer os.RemoveAll(tmpDir)
		name, content := ExportMarkdownHTML(id, tmpDir, true, merge, mergeHeadingOptions...)
		content = strings.ReplaceAll(content, "  \n", "<br>\n")

View on GitHub (pinned to afa823b6b4)