Content format
How posts, pages, categories, tags, custom content and media are stored in content/.
Everything on your website is a Markdown file in content/. You can create and
edit these files with any text editor, upload them via FTP or keep them in Git.
Wake notices changes on the next page view.
Every entry is a folder: pages, posts, categories, tags and custom types. Folder names are lowercase words joined by hyphens, without numbers or dates, and the folder name is the address (decision 0011).
content/
website.md site title and description (default language)
website.de.md … and per further language
pages/
home/content.md the start page (/)
about/ /about
content.md front matter and text, default language
content.de.md the German translation
style.css optional design for this page only
team/content.md a sub-page: /about/team
imprint/content.md /imprint
posts/
hello-world/content.md /hello-world
categories/
events/content.md a category, see below
tags/
travel/content.md optional details for the tag "travel"
events/ a custom content type (see below)
type.yml its fields and address pattern
spring-fair/content.md
media/ all images and documents, see below
posts/hello-world/cover.jpg
documents/statutes.pdf
A file
---
title: Hello world
slug: hello-world
status: published
published: 2026-10-17 09:00
tags: [travel, ruhr]
cover: media/posts/hello-world/cover.jpg
excerpt: Why I built my own CMS.
---
The first paragraph …
The part between the --- lines is the front matter in
YAML. Below it comes the text in Markdown.
Fields every entry understands
| Field | Description |
|---|---|
title |
Title of the entry. Without it, Wake uses the folder name. |
slug |
Last part of the address. Without it, Wake uses the folder name. Each language can have its own slug. |
status |
draft (only visible in the panel), scheduled (visible from published on) or published (default). |
published |
Date and time of publication, e.g. 2026-10-17 09:00, in the site's time zone. The panel fills it in when you publish. |
tags |
List of tags. Written in any spelling, Ruhr Area and ruhr-area are the same tag. |
cover |
The header image, shown large at the top: media/…, or a file name in the entry's own media folder. |
thumbnail |
The preview image for lists, cards and social media. Without it, the header image is used. |
order |
Pages: position among their siblings in the page tree (1, 2, 3 …). Pages without it come after, by name. The panel writes it when you drag pages. |
menu |
Pages: true puts the page into the automatic main menu. |
excerpt |
Short summary for lists, feeds and search engines. Without it, Wake takes the start of the text. |
template |
Render this entry with another template of the theme, e.g. template: landing. |
seo.title, seo.description |
Override title and description for search engines. |
noindex |
true keeps the entry out of the sitemap and asks search engines not to index it. |
Any other field is available in templates as well: location: Town hall is
$entry->location.
Folders and files
- A folder with a
content.md(orcontent.<lang>.md) is an entry. The id of the entry is its path belowcontent/, e.g.pages/about/team. - Folder names:
a-z,0-9and hyphens. Writeannual-meeting-2026rather than a date in front; the date of a post ispublished:. pages/homeis the start page, shown at/.- Sub-folders of a page are its sub-pages. Posts, tags and custom entries have no sub-entries.
- The order of pages and the automatic main menu come from
order:andmenu: trueincontent.md. Unlesssettings/menus.ymlhas a main menu, top-level pages withmenu: trueand their sub-pages withmenu: truemake up$site->menu(). - Moving a page in the panel writes
order:; only moving it under another page renames the folder, and Wake then writes a redirect from the old address. - Other files in an entry's folder are ignored, apart from
style.css.
Design of a single page: style.css
A style.css next to content.md applies to that entry only. Everyone who may
edit the text may edit it, in the editor under Design or by FTP. Wake loads
it only on that page and limits it with @scope (main) to the content, so the
header, menu and footer of the theme stay as they are. Wake cleans it on saving
and again on output:
@import,@charsetand@namespaceare removed,url()works only for the media library:url(media/posts/x/wave.svg)becomes the address of that file; anything else becomesnone,image-set(),expression(),behaviorand-moz-bindingare removed,<is escaped and braces are balanced, so nothing ends the<style>element or escapes the scope,- at most 64 KB are read.
/* content/pages/about/style.css */
h2 { letter-spacing: .02em; }
.callout { background: url(media/pages/about/wave.svg) no-repeat right; }
Menus
settings/menus.yml holds menus arranged in the panel (Menus). Wake knows
main and footer. Without main, the main menu follows the page tree.
main:
- page: pages/about
children:
- page: pages/about/team
- page: pages/contact
label: { en: Write to us, de: Schreib uns }
- label: Shop
url: https://shop.example.org
footer:
- page: pages/imprint
pageis the folder of a page belowcontent/. Wake updates it when the page moves in the page tree.labelis optional for pages (their title is used) and required for links; it is a text or one text per language.childrenholds one level of sub-items.- Pages that are not published are left out.
- Changes made by hand are noticed like changes to content: on the next uncached request, then the page cache is cleared.
Languages
Each language is one file in the entry's folder: content.md for the default
language and content.<code>.md for the others, e.g. content.de.md. The codes
are the ones from the languages setting. The folder,
style.css and the media are shared by all languages.
- The default language has no prefix in the URL (
/about), the others do (/de/ueber-uns). - Give translations their own
slugfor readable addresses in every language. - Files in languages the site doesn't have are ignored.
- Fields missing in a translation are taken from the default language. That is
handy for values like
startdates, which are the same in every language.
Media
All images and documents live in one library, content/media/, with
sub-folders as you like (media/documents/, media/summer-fest-2026/). The
editor uploads to media/<entry id>/, e.g. media/posts/harbour/. Refer to a
file with its path starting at media/, or, for files in the entry's own media
folder, by its name:

[Download the statutes](media/documents/statutes.pdf)
 same as media/posts/harbour/boats.jpg in posts/harbour
- Uploading a name that is taken in that folder gives it
-2. - The media library in the panel shows which entries use a file, lists unused files and warns before deleting one in use. Only people who may delete every entry may delete a file that is used.
- Files that only drafts or scheduled entries use stay private: Wake shows them to team members who may read such an entry, but nobody else, and makes no public copy. Once one entry using a file is published, the file is public; when it goes offline again, Wake removes the public copies. Files that no entry uses are public, so links from elsewhere keep working.
Wake turns these references into public URLs. Images are resized on first use to the
widths in images.widths and converted to WebP for browsers that support it.
File details
Alt text, caption, credit and focus point of a file live in a small YAML file
next to it, named after the file plus .yml. The panel writes it under
Media when you click a file; you can also write it by hand.
# content/media/posts/harbour/harbour.jpg.yml
alt:
en: Fishing boats in the harbour at night
de: Fischerboote nachts im Hafen
caption: The old harbour, October 2026
credit: "Photo: Jane Doe"
focus: 30% 60%
altandcaptionare a text, or one text per language.focusis the most important spot of an image, from the left and from the top. Themes crop images around it (CSSobject-position). Without it, the middle (50% 50%) is used.- An image in Markdown without its own alt text (
) gets the alt text from the details. - Deleting the file in the panel moves its details to the trash with it.
Cards
Cards are richer blocks inside the text. In the file they stay readable:
::: callout 💡
Text inside the card, with **Markdown**.
:::
::: button href="/join"
Become a member
:::
::: divider
:::
A card starts with ::: followed by its name and optional settings
(key="value" and free text), and ends with a line containing only :::.
A card without content can also be written on one line:
::: privacy-details :::. Cards cannot be nested.
| Card | Settings | Result |
|---|---|---|
callout |
an emoji or icon | highlighted box |
button |
href, label as text |
link styled as a button |
divider |
horizontal line | |
embed |
the address of a video, map or post, caption as text | two-click embed, see consent |
privacy-details |
Wake's always current section on statistics, cookies and embedded services, for the privacy policy |
Any other name becomes <div class="card card-{name}">, and a theme can give it
its own look with a template in templates/cards/{name}.blade.php
(see templates). With a
description next to it, the editor shows a
form for the card's settings and offers it in the / menu.
Categories
A post can have one category, given by its key:
---
title: Summer fest
category: termine
start: 2026-07-04 15:00
location: Park
---
Each category is a folder in content/categories/ (or made in the panel under
Categories). The folder name is its key. content.md holds the fields in the
default language, content.<lang>.md the translated title, slug and
description:
# content/categories/termine/content.md
---
title: Events # name in the default language
slug: events # address of the category page
description: What happens when and where.
icon: calendar
template: event # post-event and category-event templates
url: '{category}/{slug}' # optional: addresses below the category
list: upcoming # newest (default) or upcoming
date_field: start # for "upcoming"
fields:
start:
type: datetime # text, textarea, number, toggle, date, datetime, select, url, email
label: { de: Beginn, en: Start }
required: true # needed before publishing
translate: false # one value for all languages
event_status:
type: select
options:
scheduled: { de: Findet statt, en: Takes place }
cancelled: { de: Abgesagt, en: Cancelled }
---
# content/categories/termine/content.de.md
---
title: Termine
slug: termine
description: Was wann und wo stattfindet.
---
A field with list: true (type textarea) is edited one item per line and
stored as a YAML list.
Recurring events
Any entry with a start field can repeat:
start: 2026-11-03 19:00
end: 2026-11-03 21:00 # the duration applies to every date
repeat: weekly # weekly, biweekly, monthly_date, monthly_weekday, yearly
repeat_until: 2027-06-29 # optional
repeat_except: [2026-12-22, 2026-12-29]
monthly_weekday takes the weekday and its position from start: a start on
the second Tuesday repeats on every second Tuesday; a start on the last Tuesday
of a month repeats on every last Tuesday. monthly_date skips months that are
too short (no 31 February).
Values are stored in the post's front matter: dates as 2026-07-04 15:00,
yes/no fields as true. Fields with translate: false are written in the
default language only; translations read them from there.
url accepts {category} (the category's address in the entry's language),
{slug}, {year}, {month} and {day}. Without it, posts use post_url.
The category page lives at the category's address, e.g. /termine. Pages and
posts with the same address win.
Tags
A tag exists as soon as a published post uses it, and gets a page at
/tag/{slug}. A folder content/tags/{slug}/ with a content.md is optional
and adds a nicer title, a description in its body or another slug.
Custom content types
A folder in content/ with a type.yml is a new content type. The folder is
where its entries live:
# content/events/type.yml
name: event # optional, the folder name otherwise
title: Events
url: events/{slug}
fields:
start: { type: datetime, required: true }
location: { type: text }
Entries then live in content/events/ exactly like posts, and are available as
$site->entries('event') in templates. url accepts the same placeholders as
post_url. Without url, the address is {folder}/{slug}.
The fields describe the form in the panel. A type.yml in content/posts/,
content/pages/ or content/tags/ adds fields to the built-in types.
source: docs/reference/content-format.md