Templates & Filters

The generator uses Jinja2 templates to produce .qmd stub files. One built-in template exists per artifact type; all can be overridden locally.


Built-in templates

Template Used for
slides.qmd.j2 type: slides artifacts
handout.qmd.j2 type: handout artifacts
notes.qmd.j2 type: notes artifacts
assignment.qmd.j2 type: assignment artifacts
syllabus.qmd.j2 type: syllabus artifacts
module.qmd.j2 Module landing pages
index.qmd.j2 Course website home page
default_template.qmd.j2 Any unknown artifact type
_quarto.yml.j2 Root Quarto project config (generated once)
_quarto-nav.yml.j2 Navigation config (always regenerated)
_quarto-format.yml.j2 Root format config (always regenerated)
slides-project.yml.j2 Slides sub-project config (always regenerated)
handouts-project.yml.j2 Handouts sub-project config (always regenerated)
assignments-project.yml.j2 Assignments sub-project config (always regenerated)

Template resolution order

For each enabled artifact the generator searches for its Jinja2 template using this priority order (first match wins):

  1. {artifact_id}.{output_format}.qmd.j2 — id + format specific
  2. {artifact_id}.qmd.j2 — id specific
  3. {artifact_type}.{output_format}.qmd.j2 — type + format specific
  4. {artifact_type}.qmd.j2 — type specific (e.g. slides.qmd.j2)
  5. default_template.qmd.j2 — generic fallback

Templates are searched across all template directories in order: local overlay directories first, built-in package templates last.

Pinning a template in course.yaml

artifacts:
  - id: my-poster
    type: slides
    output_formats: [revealjs]
    stub_template: poster.qmd.j2          # single template for all formats

  - id: lecture-slides
    type: slides
    output_formats: [revealjs, beamer]
    stub_template:
      default: slides.qmd.j2
      beamer:  slides-beamer.qmd.j2       # per-format override

When stub_template is a string, it is used for all formats. When it is a dict, format-specific keys are tried first, then default.


Local template overrides

quarto-coursegen init copies all built-in templates into templates/ in your course project. Edit any of them freely — the generator searches that directory before the built-in ones.

You can also pass a custom directory at the command line:

quarto-coursegen generate --templates-dir /path/to/my/templates

Or configure it persistently in coursegen.yaml:

templates: templates/

Jinja2 environment

All templates have access to the following variables (exact set varies by template):

  • course — the full parsed course.yaml course: block
  • modules — list of all module dicts (after defaults have been applied)
  • module — current module dict (for per-module templates)
  • artifact — current artifact dict (for per-artifact templates)
  • i18n — resolved i18n strings dict

Custom Jinja2 filters

Six custom filters are available in all templates. Most are for working with artifact lists, and datefmt is for display date formatting. They are especially useful in module.qmd.j2, syllabus.qmd.j2, index.qmd.j2, and _quarto-nav.yml.j2.

of_type(type_name)

Filters a list of artifacts to those matching a given type.

{# All enabled slides for this module #}
{% set slides_list = module.artifacts | of_type("slides") | enabled %}
{% if slides_list %}
{% set slides = slides_list | first %}
[View slides]({{ slides.file | basename | replace('.qmd', '.html') }})
{% endif %}

enabled

Filters a list of artifacts to those with enabled: true (the default when enabled is absent).

{# Loop over all materials that should be shown #}
{% for artifact in module.artifacts | enabled %}
- **{{ artifact.type | capitalize }}**: {{ artifact.file }}
{% endfor %}

visible

Filters a list of artifacts to those with visibility not equal to hidden. This is the filter used to keep hidden items out of the public course site while still allowing them to exist as generated files when enabled: true.

{% for artifact in module.artifacts | enabled | visible %}
- {{ artifact.id }}
{% endfor %}

by_id(artifact_id)

Returns the first artifact with the given id, or None if not found.

{# Reference a specific artifact by id #}
{% set overview = module.artifacts | by_id("intro-slides") %}
{% if overview %}…{% endif %}

basename

Returns the filename component of a path string.

{{ artifact.file | basename }}
{# content/slides/intro-slides.qmd → intro-slides.qmd #}

datefmt(format_string)

Formats date-like values for display. Works with parsed YAML dates and ISO date strings.

{{ module.date | datefmt(course.date_format | default('%Y-%m-%d')) }}

Example output with course.date_format: "%d.%m.%Y":

16.04.2026

Use this for human-facing date text. For front matter date: fields, keep machine-readable source dates.

Combining filters

Jinja2’s built-in first filter complements these well:

{% set deck = module.artifacts | of_type("slides") | enabled | first %}
{% if deck %}
- [Slides]({{ deck.file | basename | replace('.qmd', '.html') }})
{% endif %}