Skip to main content

Custom Listing Template Is Not a Doctemplate

listing
A type: custom listing template has no doctemplate directives or still contains Quarto 1 EJS markup, so Quarto 2 skipped the listing rather than copy the file into the page.

Q-12-24 — Custom Listing Template Is Not a Doctemplate

A type: custom listing template has no doctemplate directives or still contains Quarto 1 EJS markup, so Quarto 2 skipped the listing rather than copy the file into the page.

What this means

Quarto 2 renders custom listings with doctemplates — the same $variable$ / $if(…)$ / $for(…)$ syntax Pandoc templates use. The template you named in template: compiled, but either

  • it contains no directives at all — every byte is literal text, so rendering it would just paste the file into your page, or
  • it contains <% … %> — Quarto 1 EJS markup, which Quarto 2 does not execute. Left in place, it would appear verbatim to readers (and link checkers would report href="<%= item.link %>").

Rather than publish the raw file, Quarto 2 emits this warning and leaves the listing out of the page.

Why this happens

  • A Quarto 1 custom listing template carried over unchanged. Q1 listing templates are EJS (welcome-card.ejs, item.ejs.md). Quarto 2 does not run EJS; see also Q-12-9, which fires on the .ejs / .ejs.md extension at configuration time.
  • A partially ported template — some <%= … %> expressions were rewritten to $…$, others were missed.
  • A static file named by mistake — template: points at a plain markdown or HTML file with nothing to interpolate.

How to fix

Rewrite the template as a doctemplate. The common mappings:

Quarto 1 (EJS) Quarto 2 (doctemplate)
<%= item.title %> $it.title$ (inside $for(items)$)
<% for (const item of items) { %> … <% } %> $for(items)$ … $endfor$
<% if (item.image) { %> … <% } %> $if(it.image)$ … $endif$
<%= item.myfield %> (custom field) $it.myfield$ or $it.extra.myfield$
<a href="<%- item.path %>"> [$it.title$]($it.path$) — a markdown link, not a raw anchor
<img src="<%= item.image %>"> ![$it.image-alt$]($it.image$) — a markdown image, not a raw <img>
JavaScript expressions Pre-compute the value into a listing field

Before:

<div class="cards">
<% for (const item of items) { %>
  <a href="<%= item.link %>"><%= item.title %></a>
<% } %>
</div>

After:

::: {.cards}
$for(items)$
[$it.title$]($it.path$)
$endfor$
:::

Two changes in that example are not cosmetic. The <div> became a fenced div, whose contents Quarto parses as markdown; and the raw <a href> became a markdown link. That second change is what makes the href work. A template’s output is markdown, and Quarto rewrites .qmd link targets only after parsing it — so a raw-HTML anchor keeps the source path and ships a dead href, with no warning. Images behave the same way, and cost more: an image written as raw HTML is never copied into the site at all.

Listing templates covers both rules and works through a full port. The Listings guide documents the values a template can read.