`body-header/footer` and `margin-header/footer` do not resolve `/`-prefixed paths as project-relative
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
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:
expandMarkdownFilePathtreats/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.
resolveProjectPathsonly applied tomargin-header/margin-footer, notbody-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.
- Discussion: https://github.com/quarto-dev/quarto-cli/discussions/6566
- Issue #13270 (documentation for these options)
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.htmlis 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 forindex.qmdat the project root, but forpages/mypage.qmdthe path resolves topages/_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.htmlshould resolve the path relative to the project root directory, following Quarto's convention for/-prefixed paths.body-footer: _footer.htmlshould 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from quarto-dev/quarto-cli
-
binder bug
Difficulty 1/5 Under an hour Newbie friendliness 92/100
quarto-dev/quarto-cli#14907 · 3 comments ·
-
brand bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
quarto-dev/quarto-cli#14891 ·
-
brand bug html revealjs
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
quarto-dev/quarto-cli#14882 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
quarto-dev/quarto-cli#14875 ·
-
Percent scripts: accept raw-string (r""") and '''-delimited markdown cells, as jupytext writes them Openengines-jupyter enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
quarto-dev/quarto-cli#14850 · 2 comments ·
All issues in quarto-dev/quarto-cli
Similar issues
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
babalae/bettergi-scripts-list#3674 ·
-
ecosystem wording
Difficulty 1/5 Under an hour Newbie friendliness 90/100
matrix-org/matrix.org#3649 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
vadimdemedes/ink#1029 ·
-
code-quality refactoring
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
github/gh-aw-firewall#8816 ·
-
integration:quickjs org:external priority:backlog topic:code-interpreter topic:middleware type:feature
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
langchain-ai/deepagents#6450 ·