The default page shell, section renderer, link renderer, shared templates, CSS, JS, and base assets.
webifier.standard is the foundation extension most sites enable first. It
supplies the default page shell, section renderer, content-page shell, link
renderer, freeform renderer, shared navigation/header/footer macros, base
CSS, syntax highlighting, and code-block copy behavior.
config:
webifier:
extensions:
site:
uses: webifier.standard
A Webifier page starts as an ordered YAML mapping. Webifier walks it from top to bottom and renders each non-reserved key.
kind switches a dictionary to another renderer.template switches the template used for a page or section.That means a page is mostly a readable outline: write the content in the order you want people to read it, then add renderer hints only where the default behavior is not enough.
These top-level keys configure the page instead of rendering as ordinary sections:
title: browser title and fallback page title.header: page hero/header data.nav: navigation links.footer: footer data.meta: HTML meta tags for the page template.config: page or site configuration.search: search behavior for the page.style: page-level style data for templates.kind: page renderer id.template: page template override.Every other top-level key renders as content in YAML order.
Content-page renderers can read page-local controls from the reserved
config key. The same contract is used for Markdown page prefaces,
notebook first-cell page prefaces, PDF sibling page.yml files, and normal
YAML pages:
title: Page title
config:
content_pages:
toc: true
markdown:
toc: true
notebook:
toc: true
colab: false
pdf:
toc: false
download: false
related:
kind: links
items:
- text: Another page
src: pages/user-guide/index.yml
config is never rendered as content. It is merged over the site-level
config for that one page, then extensions read the parts they own. In the
example above, content_pages, markdown, notebook, and pdf are
extension-owned config namespaces.
Free-form keys outside config, such as related, are normal page
sections. For Markdown and notebooks, those extra sections render after the
main file body, which lets a content file carry authors, comments, related
links, or custom extension sections without mixing them into the prose.
The important distinction is:
config.config.---
title: A Markdown Page
config:
content_pages:
toc: true
markdown:
toc: true
authors:
kind: people
content:
- name: Vahid Zehtab
role: Author
website: https://vahidz.com
comments:
kind: comments
label: false
---
# Body
In this example, config.markdown and config.content_pages control the
generated content page. The authors and comments keys render after the
body using normal section renderers.
A normal section is also just a dictionary. The standard section renderer consumes a few reserved keys and treats everything else as children.
section_name:
label: Optional visible label
background: optional/path/to/image.png
template: optional-inner-template.html
kind: section
first_child: |
Markdown content.
second_child:
label: Nested section
content: |
A nested child section.
Section-level reserved keys are:
label: string, false, or {text, position}.background: image path copied into the built site and used behind the section.template: inner template for this section, while keeping section chrome.kind: renderer id for this block.style, freeform, defaults: reserved for renderer/template use.Any non-reserved key inside a section becomes a child. Here, content is
not magic; it is simply a child key whose value is Markdown.
introduction:
label: Introduction
content: |
This is a normal section. Markdown is allowed here.
- Lists work.
- Inline `code` works.
- Inline math like \(E = mc^2\) and display math work.
- Fenced blocks get highlighting and copy buttons.
\[
\nabla_\theta \mathcal{L}(\theta)
=
\frac{1}{N}\sum_{i=1}^{N}\nabla_\theta \ell_i(\theta)
\]
```mermaid
flowchart LR
A["Markdown"] --> B["MathJax"]
A --> C["Mermaid"]
A --> D["Highlighted code"]
```
```yaml
hello: world
nested:
value: 42
```
This is a normal section. Markdown is allowed here.
code works.hello: world
nested:
value: 42
Labels can be shown on the left, top, or bottom. They can also be disabled when a section should behave like a plain content panel.
label_examples:
label:
text: Label examples
position: top
left_label:
label: Left label
content: Left labels are the default.
top_label:
label:
text: Top label
position: top
content: Top labels span the content width.
bottom_label:
label:
text: Bottom label
position: bottom
content: Bottom labels appear after the content panel.
no_label:
label: false
content: This child has no visible label.
Left labels are the default section style. They work well for scan-heavy pages where each content band needs a stable name.
Top labels are useful when the section content needs the full width.
Bottom labels are available when the label should feel like a caption.
Disabled labels keep the standard content panel but remove the visible label column.
Nesting is recursive. Any child dictionary without another kind becomes
another standard section. This lets you structure pages into chapters,
subchapters, callouts, examples, or result panels without memorizing a
special schema.
chapter:
label: Chapter
opening: |
Introductory Markdown for the chapter.
subsection:
label:
text: Subsection
position: top
note: |
Nested content can contain more Markdown.
deeper:
label: Deeper
content: |
This is a nested nested section.
This is the parent section. The blocks below are children rendered by the same section renderer.
Nested content can contain Markdown, lists, code, and more sections.
This is a nested nested section. The layout stays predictable because each dictionary is dispatched through the same renderer contract.
kind is how you switch behavior. The standard extension registers
section, links, and freeform; other extensions register things like
people, experience, publications, comments, notebook, or pdf.
resources:
kind: links
items:
- text: User guide
src: pages/user-guide/index.yml
- text: Markdown example
src: pages/user-guide/extensions/examples/tiny-markdown.md
- text: Tiny PDF
src: pages/user-guide/extensions/examples/tiny.pdf
raw_intro:
kind: freeform
html: |
<div class="alert alert-secondary">
Freeform content skips the section wrapper.
</div>
The explicit kind: links block below is rendered directly as a list group.
Start with repository files, then grow toward extension-defined page syntax, renderers, themes, and reusable formats.
background accepts a local image path or an external image URL and
applies it to the section. Local image paths are copied into the built site.
It is a section-level visual option, so nested sections can use it too.
background_example:
label:
text: Background example
position: top
background: https://images.unsplash.com/photo-1515879218367-8466d910aaa4?auto=format&fit=crop&w=1600&q=80
content: |
This content is still ordinary Markdown.
This section uses an external background URL. The content remains regular Markdown inside the standard section chrome.
At the page root, template replaces the page template. Inside a section,
template only replaces the section's inner content. The label,
background, spacing, and section wrapper still come from the standard
section renderer.
Templates are written in
Jinja2, the Python
templating engine Webifier uses under the hood. A section template receives
the section's non-reserved YAML keys as data, and can use helpers such as
markdown(...) to render Markdown inside the template.
For the full customization path, see Template Overrides, Template-Only Renderers, and the lower-level Renderer / Kind System.
templated_callout:
label: Template
template: renderers/standard-callout.html
eyebrow: Example
title: Inner template override
body: |
The template receives these keys as `data` and can call helpers such as
`markdown(...)`.
Let's say your website has this renderer template at
renderers/standard-callout.html:
<div class="border-start border-4 border-primary-subtle ps-3 py-2">
{% if data.eyebrow %}
<p class="text-uppercase small text-muted fw-semibold mb-1">
{{ data.eyebrow }}
</p>
{% endif %}
{% if data.title %}
<h3 class="h5 mb-2">{{ data.title }}</h3>
{% endif %}
{% if data.body %}
<div class="mb-0">{{ markdown(data.body) }}</div>
{% endif %}
</div>
data.eyebrow, data.title, and data.body come directly from the YAML
keys under the templated section. The {% if ... %} blocks are normal
Jinja control flow, and {{ ... }} prints a value into the output.
Example
The section still owns the label and outer panel, while
renderers/standard-callout.html controls the content inside it.
webifier.standard contributes default renderer ids:
defaults:
page: page
section: section
links: links
markdown: markdown
You can override those defaults in config, or install a later extension
instance with override: true to replace renderer registrations.
config:
webifier:
extensions:
site:
uses: webifier.standard
lab_site:
uses: my_lab.standard
override: true
defaults:
section: my-lab-section
The deeper extension contract is documented in the Extension and Customization Guide. For site-level defaults and extension configuration, see Configuration Basics.
The practical syntax is:
<page key>: <string> renders Markdown.<page key>: <list> renders links.<page key>: <dict> renders a section.label: "Name" sets the visible section label.label: false hides the label.label: {text: "Name", position: top} puts the label above content.label: {text: "Name", position: bottom} puts the label below content.background: path/to/image sets a section background image.template: path/to/template.html changes section inner rendering.kind: links renders a direct link list.kind: freeform renders content without the standard section wrapper.kind is handled by whichever extension registered that renderer.config.markdown.toc, config.notebook.toc, and config.pdf.toc control generated tables of contents for those page types.config.content_pages.toc is the shared lower-level content-page control.config.content_pages.cleanup enables shared content cleanup for generated content pages.When the standard renderer stops being enough, create an extension that
registers a new kind and give that renderer the schema you actually want.