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