`body-header/footer` and `margin-header/footer` do not resolve `/`-prefixed paths as project-relative

Open
#14,057 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
48/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Stale
Tech stack
typescript
Domain
cli

Research direction

Read expandMarkdownFilePath in src/project/types/website/website-navigation-md.ts and resolveProjectPaths in src/project/types/website/website-config.ts, then reproduce the issue with the provided website structure and quarto render. Trace how all four header and footer options are resolved. Done means leading-slash paths resolve from the project root and the four options use consistent path resolution.

Written by the indexing model from the issue text.

Description

enhancement html layout websites
Bug description

body-header, body-footer, margin-header, and margin-footer do not follow Quarto's path resolution convention for paths starting with /.

In Quarto, a leading / in a path means "relative to the project root" (similar to here::here() in R). However, these options treat /path as a filesystem-absolute path instead.

There are two distinct problems in the path resolution:

  1. expandMarkdownFilePath treats / as filesystem-absolute

The function that resolves file paths for all four options (body-header, body-footer, margin-header, margin-footer) uses isAbsolute(path) to decide whether to join with the source directory. When a path starts with /, it is treated as a filesystem-absolute path. Since the file does not exist at the filesystem root, the raw path string is inserted as markdown content instead of the file contents.

  1. resolveProjectPaths only applied to margin-header/margin-footer, not body-header/body-footer

The resolveProjectPaths helper in website-config.ts is only applied to margin-header and margin-footer. It is never applied to body-header or body-footer, meaning these two options skip the path resolution step entirely. Additionally, resolveProjectPaths itself has the same filesystem-root issue: it calls existsSync(maybePath) on the raw value, so /footer.html checks if /footer.html exists on the filesystem root rather than resolving it relative to the project directory.

Steps to reproduce

Create a Quarto website project with the following structure:

project/
├── _quarto.yml
├── _footer.html
├── index.qmd
└── pages/
    └── mypage.qmd

_footer.html:

<footer>Custom footer content</footer>

_quarto.yml:

project:
  type: website

website:
  title: "Demo"
  navbar:
    left:
      - href: index.qmd
        text: Home
      - href: pages/mypage.qmd
        text: My Page
  body-footer: /_footer.html

format:
  html:
    theme: cosmo

index.qmd:

---
title: "Home"
---

Home page content.

pages/mypage.qmd:

---
title: "My Page"
---

Page in subdirectory.

Then render with quarto render.

Actual behavior
  • With body-footer: /_footer.html (leading /): the literal string /_footer.html is rendered as text in the footer on all pages, because the path is treated as filesystem-absolute and the file is not found.
  • With body-footer: _footer.html (no leading /): works for index.qmd at the project root, but for pages/mypage.qmd the path resolves to pages/_footer.html (relative to the source file), which does not exist, so the literal string is rendered as text.

The same behaviour applies to body-header, margin-header, and margin-footer.

Expected behavior
  • body-footer: /_footer.html should resolve the path relative to the project root directory, following Quarto's convention for /-prefixed paths.
  • body-footer: _footer.html should also resolve relative to the project root (since it is set in _quarto.yml), or at least the documentation should clarify that relative paths are resolved relative to each source file.
  • All four options (body-header, body-footer, margin-header, margin-footer) should follow the same path resolution logic consistently.
Quarto check output
Quarto 99.9.9
[✓] Checking environment information...
      Quarto cache location: /Users/mcanouil/Library/Caches/quarto
[✓] Checking versions of quarto binary dependencies...
      Pandoc version 3.8.3: OK
      Dart Sass version 1.87.0: OK
      Deno version 2.4.5: OK
      Typst version 0.14.2: OK
[✓] Checking versions of quarto dependencies......OK
[✓] Checking Quarto installation......OK
      Version: 99.9.9
      commit: eef6d945afbdafe9dcc619c10ff97bcb8910063c
      Path: /Users/mcanouil/Projects/quarto-dev/quarto-cli/package/dist/bin

[✓] Checking tools....................OK
      TinyTeX: v2026.02
      VeraPDF: 1.28.2
      Chromium: (not installed)
      Chrome Headless Shell: (not installed)

[✓] Checking LaTeX....................OK
      Using: TinyTex
      Path: /Users/mcanouil/Library/TinyTeX/bin/universal-darwin
      Version: 2025

[✓] Checking Chrome Headless....................OK
      Using: Chrome from QUARTO_CHROMIUM
      Path: /Applications/Brave Browser.app/Contents/MacOS/Brave Browser

[✓] Checking basic markdown render....OK

(-) Checking R installation...........ℹ R version 4.5.2 (2025-10-31)
! Config '~/.Rprofile' was loaded!
[✓] Checking R installation...........OK
      Version: 4.5.2
      Path: /Library/Frameworks/R.framework/Resources
      LibPaths:
        - /Users/mcanouil/Projects/quarto-dev/quarto-playground/renv/library/macos/R-4.5/aarch64-apple-darwin20
        - /Users/mcanouil/Library/Caches/org.R-project.R/R/renv/sandbox/macos/R-4.5/aarch64-apple-darwin20/4cd76b74
      knitr: 1.50
      rmarkdown: 2.30

[✓] Checking Knitr engine render......OK

[✓] Checking Python 3 installation....OK
      Version: 3.14.0
      Path: /Users/mcanouil/Projects/quarto-dev/quarto-playground/.venv/bin/python3
      Jupyter: 5.9.1
      Kernels: uv, julia-1.12, python3

[✓] Checking Jupyter engine render....OK

[✓] Checking Julia installation...
Dominant language
JavaScript
Stars
6k
Forks
458
Avg merge
1d 9h
Merged PRs (30d)
41

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from quarto-dev/quarto-cli

All issues in quarto-dev/quarto-cli

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.