Speicher reads CommonMark with the usual GitHub additions, plus callouts, wikilinks, footnotes and a set of MDX components. Pages render to plain HTML, so everything here works without JavaScript.
By default, Speicher reads every .md and .mdx file in the repository except README.md files. To change that, set sync in .speicher.yaml.
Pages and folders
- Every Markdown file becomes a page, and every folder becomes a section in the navigation.
- A number prefix of one to three digits sets the order and is left out of the URL:
01-install.mdis served as/install. A four-digit number such as2024-review.mdis not a prefix, so the page keeps it in its URL and sorts by title. index.md(orindex.mdx) is the folder's own page. A file named like a folder next to it, such asguide.mdbesideguide/, works the same way.- A page without a prefix is sorted by its title, after the numbered ones.
Page title
The title comes from the first of these that exists:
titlein the frontmatter- the first
# Headingof the page - the file name, without prefix and extension
The first # Heading is shown as the page title, so Speicher removes it from the page body. Start your sections with ##.
Frontmatter
A page can start with a YAML block between --- lines:
title: Deploying to production
description: How a release reaches the servers.
icon: rocket
tags: [operations, release]
redirect_from: [/handbook/ops/deploy]
draft: false
descriptionis shown in search results.iconis the name of a Lucide icon (opens in a new tab), shown next to the page in the navigation. An unknown name is ignored with a warning.tagslink pages on the same topic. They have no effect on who can read the page.redirect_fromlists old addresses that should redirect here, for example after moving a page. Write the full path as it appears in the URL, including the repository, such as/handbook/ops/deployor/acme/handbook/ops/deploy.draft: trueleaves the file out completely.- Keys Speicher doesn't know are ignored, and the sync warns about them.
All keys are listed under Frontmatter. Frontmatter cannot change who can read a page; that is what .speicher.yaml is for.
Caution
If the frontmatter is not valid YAML, Speicher rejects the file and reports the error on the Sync status page. The rest of its folder is not updated in that sync.
Headings and anchors
Every heading gets an anchor, so you can link to #install on a page:
| Heading | Anchor |
|---|---|
## Install |
#install |
## Über uns |
#ueber-uns |
## Setup & Upgrade |
#setup--upgrade |
a second ## Install |
#install-1 |
The anchor is the heading in lowercase, with German umlauts written out, punctuation removed and spaces turned into hyphens. To choose an anchor yourself, write the heading as HTML: <h2 id="install-steps">Install</h2>.
Links
Link to another page by its file path, relative to the current file, including the extension:
[Installation](./02-install.md)
[Upgrading](../operations/upgrade.md#before-you-start)
[Guide](./guide/index.md)
[Handbook home](/index.md)
- A path starting with
/starts at the repository root. - To link to a folder, link to its
index.md. - A link without the extension, such as
./02-install, does not find the page.
Links to other files, such as a PDF, serve that file under the same access rules as the folder it is in. Links to websites open in a new tab.
A link to a file that doesn't exist is shown as a broken link, and the sync reports it as a warning. The sync also warns when a link points to a page that fewer people can read than the page the link is on.
Links to other repositories
Start the address with @, followed by the repository and the page's path in that repository's URL:
[dotenvx](@kb-dev-tools:dotenvx)
[Tolerances](@northwind-engineering/specs:tolerances)
[Docs on another source](@gitlab/acme/docs:getting-started)
Use one, two or three parts before the colon: the repository, the owner and repository, or the content source, owner and repository.
Wikilinks
If you write in Obsidian, you can keep its link syntax:
[[Setup Guide]]
[[guide/setup]]
[[setup|the setup page]]
[[setup#install]]
![[diagrams/architecture.png]]
Speicher looks for the target relative to the current file, then from the repository root, then by file name anywhere in the repository. The extension is optional. If several files have the same name, the link goes to the first one, and the sync warns you to write a path instead.
![[image.png]] shows the image. ![[Some page]] cannot show a page inside another yet, so it becomes a link to that page.
Images and files

- Supported images are PNG, JPEG, GIF, WebP, AVIF, BMP, ICO and SVG.
- Images must be committed to the repository. Images from other websites are not shown, and the sync reports them.
- An image is served under the access rules of the folder it is in, not those of the page that shows it.
- Files above the instance's size limit (10 MB by default) are left out with a warning.
To set an image's size, write it as HTML: <img src="./img/logo.png" alt="Logo" width="120">.
Code blocks
Name the language after the opening fence to highlight the code:
```ts
const answer = 42;
```
Highlighted languages are Bash, C, C++, C#, CSS, Dart, Diff, Dockerfile, Elixir, Go, GraphQL, HTTP, INI, Java, JavaScript, JSON, Kotlin, Less, Lua, Makefile, Markdown, nginx, Objective-C, Perl, PHP, PowerShell, Protobuf, Python, R, Ruby, Rust, Scala, SCSS, Shell, SQL, Swift, TypeScript, VB.NET (opens in a new tab), WebAssembly, XML/HTML and YAML, including their common short names such as ts, js, sh and yml. A block in any other language, or a block over 100 KB, is shown without highlighting.
Callouts
:::note
This page is updated with every release.
:::
:::tip[Faster syncs]
Install the GitHub App to sync on every push.
:::
:::caution Read this first
This deletes the stored content.
:::
The types are note, info, tip, important, warning, caution and danger. caution looks the same as warning. Give a title in brackets or after a space. A ::: block with any other name is shown as plain text.
GitHub's alert syntax works too:
> [!NOTE]
> Speicher reads this as a note.
Here the types are NOTE, TIP, IMPORTANT, WARNING and CAUTION. Following GitHub, CAUTION looks the same as danger.
Tables, task lists and footnotes
| Setting | Default |
| :------ | ------: |
| Port | 3000 |
- [x] Write the page
- [ ] Review it
Speicher reads Markdown.[^1]
[^1]: CommonMark, with the GitHub additions.
- Column alignment from the separator row is kept.
- Task lists show their state but cannot be ticked on the page.
- Footnotes are collected at the end of the page, with links back to where they are used.
~~strikethrough~~and bare web addresses, which become links, work as on GitHub.
HTML
You can mix HTML into Markdown. Speicher keeps text formatting and structure, such as <details>/<summary>, <kbd>, <mark>, <sub>, <sup>, <abbr>, <dl>, tables and <img>.
It removes everything that could run code or change the page's layout: <script>, <style>, <iframe>, <object>, forms, style attributes, event handlers such as onclick, and javascript: links. Classes are removed too, except the ones Speicher uses itself.
MDX components
In .mdx files you can use the following components. The names and attributes are the same as in Starlight, and Tabs/TabItem as in Docusaurus.
| Component | Attributes | Shows |
|---|---|---|
Tabs |
syncKey (optional) |
a group of tabs; groups with the same syncKey switch together |
TabItem |
label |
one tab |
Steps |
a numbered list as a sequence of steps | |
FileTree |
a nested list as a file tree | |
CardGrid |
a grid of the cards inside it | |
Card |
title |
a card with Markdown content |
LinkCard |
title, href, description (optional) |
a card that links somewhere |
Aside |
type (note, tip, caution, danger, …), title |
a callout, like :::note |
Badge |
text, variant (note, tip, caution, danger, success) |
a small label, also inside a sentence |
LinkButton |
href |
a link styled as a button |
Code |
code, lang |
a code block |
<Tabs syncKey="package-manager">
<TabItem label="npm">
```sh
npm install speicher-client
```
</TabItem>
<TabItem label="pnpm">
```sh
pnpm add speicher-client
```
</TabItem>
</Tabs>
<CardGrid>
<LinkCard title="Install" href="./01-install.mdx" description="Get it running" />
<Card title="Good to know">
Content of a card is **Markdown**.
</Card>
</CardGrid>
New in this release: <Badge text="Beta" variant="tip" />
Leave a blank line between a component's tags and Markdown content inside it, as in the example.
What Speicher does with the rest of MDX:
importandexportlines at the top of the file are ignored.- An unknown component, such as a custom
<Chart>, is left out, but the content between its tags is shown. The sync warns about it. - JavaScript expressions such as
{props.version}cannot run and are left out with a warning. Comments written as{/* … */}are left out silently. Iconis ignored, because it is decoration.- Indented text is not a code block in
.mdxfiles. Use a fenced block instead.
Coming from Docusaurus or Starlight
You can point Speicher at an existing Docusaurus or Starlight docs/ folder with ingest_root and keep writing as before:
sidebar_position(Docusaurus) andsidebar.order(Starlight) in the frontmatter set the order of pages. They are only used in folders where no file has a number prefix. If a folder mixes both, the prefixes win and the sync warns.:::note-style admonitions and Starlight's components work as described above.- Other frontmatter keys, such as
sidebar_labelorslug, are ignored with a warning, so your files don't need changes. The rest of Starlight'ssidebarblock is ignored without a warning.