Academy plugin

Manages online courses with a three-level hierarchy: course → module → lesson. Each level is a content item on its own (with its own title, description, tags, etc.), built from the filesystem structure — see Content hierarchy.

resources/content/academy/
  getting-started.md                       # Course.
  getting-started/
    introduction.md                        # Module.
    introduction/
      what-is-this.md                      # Lesson.
      how-it-works.md                      # Lesson.

Configuration (services.yaml)

Enabled under derafu.content.config.plugins.academy:

parameters:
    derafu.content.config:
        plugins:
            academy:
              path: 'resources/content/academy'
              academyTitle: 'Academy'
              academyDescription: 'Do you want to learn about a topic? Start a course with us!'
Option Type Default Description
path string resources/content/academy Directory scanned for course content, relative to the website root.
academyTitle string Academy Title used for the academy’s own SEO metadata.
academyDescription string Do you want to learn about a topic? Start a course with us! Description used for the academy’s own SEO metadata.
include array of glob patterns ['**.{markdown,md}'] Files considered content, relative to path.
exclude array of glob patterns [] Files excluded even if matched by include.
showReadingTime bool true Whether to show the estimated reading time.
showLastUpdateAuthor bool true Whether to show who last updated the lesson.
showLastUpdateTime bool true Whether to show when the lesson was last updated.
tags array of strings, or string [] Predefined tags for the academy.
onInlineTags ignore|log|warn|throw warn What to do when a lesson uses a tag that isn’t in the predefined tags list.

Content frontmatter

Courses and modules use exactly the generic content frontmatter, nothing added.

Lessons add one field:

Field Type Default Description
test string none A reference to a JSON quiz attachment: either ?attachment=<filename>, or a literal path ending in /_attachments/<filename> (see Storage) — both resolve to the same local attachment. Parsed into a structured test, shown as an interactive quiz on the lesson’s own page, and rendered in full in the PDF and Markdown exports. Also switches the lesson’s sidebar icon. A test value that resolves to neither form is passed through as-is to the quiz widget instead, unparsed.

time on a course or module is not read from its own frontmatter — it is always the sum of its lessons’ time (explicit or estimated).

Quiz JSON format

{
    "title": "Introduction quiz",
    "description": "Check what you remember from this lesson.",
    "questions": [
        {
            "type": "multiple_choice",
            "text": "Which of these is correct?",
            "options": [
                { "text": "Option A", "is_correct": true },
                { "text": "Option B", "is_correct": false }
            ],
            "allow_multiple": false,
            "explanation": "Option A is correct because..."
        },
        {
            "type": "true_false",
            "text": "This statement is true.",
            "answer": true,
            "explanation": "..."
        }
    ]
}
Field Type Description
title string Title of the test.
description string Optional description.
questions[].type multiple_choice|true_false Question type. A true_false question needs no options — its two options (“True”/“False”) are generated from answer.
questions[].options[].is_correct bool Whether this option is a correct answer. More than one can be true when allow_multiple is true.
questions[].explanation string Shown in the PDF/Markdown exports’ answer key, and in the lesson’s own interactive quiz when an answer is marked incorrect.
On this page

Last updated on 21/08/2026 by Anonymous