Das portable Format
Die Dateien sind maßgeblich. Ein Blog ist ein Verzeichnis aus Markdown-Dateien mit YAML-Frontmatter und den Fotos daneben. Die Datenbank ist ein abgeleiteter Index — sie macht Ortssuche und Volltextsuche schnell, und sie lässt sich aus den Dateien wieder aufbauen.
Das ist der ganze Eigentumsanspruch, und er ist nur etwas wert, wenn das Format aufgeschrieben ist. Also hier ist es, versioniert, mit einem Deprecation-Pfad daran. Diese Seite ist der Vertrag: Wenn du ein Theme, einen Konverter oder ein Werkzeug schreibst, das ein Ortszeit-Archiv liest, bau gegen diese Seite und nicht gegen das, was ein bestimmter Export zufällig enthalten hat.
Version
ortszeit_schema: 1
Der Schlüssel steht in blog.yaml und in posts.json. Einzelne Beitragsdateien
tragen ihn nicht — sie gehören zu dem Blog, in dem sie liegen.
Aufbau
blog/
blog.yaml the blog itself
posts.json every post again, as JSON
trips/
denmark-2026/
trip.yaml one trip
2026-06-12-aarhus-harbour.md a post in that trip
media/
IMG_4021.jpg
posts/ posts that belong to no trip
2026-05-01-a-note.md
media/
IMG_3980.jpg
- Beitragsdateien heißen
YYYY-MM-DD-<slug>.md, datiert nach demselben Wert wie das Felddatedes Beitrags — Dateiname und Frontmatter stimmen also immer überein. - Jedes Beitragsverzeichnis hat sein eigenes
media/. Mediendateinamen sind innerhalb eines solchen Verzeichnisses eindeutig; bei einer Kollision werden-2,-3und so weiter an den Namensstamm gehängt. - Reisen ohne Beiträge werden trotzdem exportiert. Eine leere Reise gehört dir genauso.
Frontmatter eines Beitrags
---
title: Aarhus harbour
date: 2026-06-12T14:20:00+02:00
draft: false
place:
name: Aarhus Havn
osm: node/240109189
lat: 56.1572
lon: 10.2107
visibility: public
media:
- file: media/IMG_4021.jpg
taken_at: 2026-06-12T14:18:00+02:00
alt: Ferry coming in past the harbour cranes
---
Rain until four, then the harbour went gold.
Feld für Feld:
| Schlüssel | Typ | Hinweise |
|---|---|---|
title |
String oder null | Immer vorhanden, oft null. Ein Beitrag ohne Titel ist normal, nicht kaputt. |
date |
Zeitstempel | Mit Zeitzone. published_at bei einem veröffentlichten Beitrag, die Erstellungszeit, solange er Entwurf ist — der Wert passt also immer zum Dateinamen. |
draft |
Boolean | Immer vorhanden. Entwürfe werden mitexportiert: Sie sind deine Texte, und ein Archiv, das sie stillschweigend weglässt, ist kein Archiv. |
place |
Mapping | Fehlt vollständig, wenn der Beitrag keinen Ort hat. Wird nie als null geschrieben. |
place.name |
String | Der angezeigte Name. |
place.osm |
String oder null | <osm_type>/<osm_id>, z. B. node/240109189. Null, wenn der Ort aus einer Quelle ohne OpenStreetMap-Referenz kam — meistens aus einem Import. |
place.lat |
Zahl | Dezimalgrad. |
place.lon |
Zahl | Dezimalgrad. Beachte: lon, nicht lng. |
visibility |
String | public, unlisted oder password. Das ist die effektive Sichtbarkeit, bereits gegen die Reise aufgelöst — ein Leseprogramm muss keine Vererbung über zwei Dateien hinweg nachvollziehen. |
media |
Liste | Immer vorhanden, möglicherweise leer. Die Reihenfolge ist die Anzeigereihenfolge. |
media[].file |
String | Pfad relativ zum eigenen Verzeichnis des Beitrags, damit er sich für Reise-Beiträge und einzelne Beiträge gleich liest. |
media[].taken_at |
Zeitstempel oder null | Aus dem EXIF des Fotos. Null, wenn die Quelle nie eine Zeit angegeben hat. |
media[].alt |
String oder null | Alternativtext. Null, wenn leer. |
Der Text ist alles nach dem schließenden ---, als reines Markdown, mit der
Teilmenge aus Erste Schritte.
blog.yaml
ortszeit_schema: 1
title: Ortszeit
bio: Notes from the road.
subdomain: alice
custom_domain: null
theme: ortszeit
custom_css: ''
exported_at: 2026-06-20T09:14:03+00:00
| Schlüssel | Typ | Hinweise |
|---|---|---|
ortszeit_schema |
Integer | Die Schemaversion dieses Archivs. |
title |
String | Blogtitel. |
bio |
String | Kurzbeschreibung. Darf leer sein. |
subdomain |
String | Der Name des Blogs unter ortszeit.app. |
custom_domain |
String oder null | Null, wenn es keine gibt. |
theme |
String | Heute ortszeit. Das Feld existiert, damit ein Theme-Ökosystem sich benennen kann. |
custom_css |
String | Darf leer sein. |
exported_at |
Zeitstempel | Wann dieses Archiv geschrieben wurde. Das einzige Feld, das vom Export handelt und nicht vom Blog. |
trip.yaml
title: Denmark 2026
slug: denmark-2026
started_on: 2026-06-10
ended_on: 2026-06-24
visibility: public
co_authors:
- Alice
| Schlüssel | Typ | Hinweise |
|---|---|---|
title |
String | |
slug |
String | Innerhalb des Blogs eindeutig; er ist zugleich der Verzeichnisname. |
started_on |
Datum | Datum, kein Zeitstempel. |
ended_on |
Datum oder null | Null, solange die Reise offen ist. |
visibility |
String | public, unlisted oder password. |
co_authors |
Liste von Strings | Anzeigenamen. |
Das Passwort einer Reise wird nie exportiert, in keinem Tarif und in keiner
Form — weder das Passwort noch sein Hash. Eine Reise mit
visibility: password kommt anderswo also ohne Schutz an, und jedes
Importprogramm muss sie als privat behandeln statt als öffentlich. Der Importer
von Ortszeit stuft sie auf unlisted herunter und sagt dir, dass du ein neues
Passwort setzen sollst.
posts.json
Derselbe Inhalt noch einmal, als ein JSON-Dokument, für Werkzeuge, die lieber kein Frontmatter parsen:
{
"ortszeit_schema": 1,
"blog": { "ortszeit_schema": 1, "title": "Ortszeit", "...": "..." },
"posts": [
{
"path": "trips/denmark-2026/2026-06-12-aarhus-harbour.md",
"slug": "aarhus-harbour",
"trip": "denmark-2026",
"title": "Aarhus harbour",
"date": "2026-06-12T14:20:00+02:00",
"draft": false,
"place": { "name": "Aarhus Havn", "osm": "node/240109189", "lat": 56.1572, "lon": 10.2107 },
"visibility": "public",
"media": [
{ "file": "media/IMG_4021.jpg", "taken_at": "2026-06-12T14:18:00+02:00", "alt": null }
],
"body": "Rain until four, then the harbour went gold."
}
]
}
Jeder Eintrag trägt das Frontmatter des Beitrags unverändert, dazu drei Schlüssel, die die Markdown-Datei stattdessen über ihren Ort im Baum ausdrückt:
| Schlüssel | Typ | Hinweise |
|---|---|---|
path |
String | Der Pfad der Beitragsdatei innerhalb von blog/. |
slug |
String | Der Slug des Beitrags. |
trip |
String oder null | Der Slug der Reise, oder null bei einem Beitrag ohne Reise. |
body |
String | Der Markdown-Text. |
Daten und Zeitstempel sind in JSON ISO-8601-Strings, während die YAML-Dateien echte Datums- und Zeitwerte verwenden.
Das Format lesen
Drei Regeln, damit ein heute geschriebenes Leseprogramm auch später noch funktioniert:
- Ignoriere Schlüssel, die du nicht kennst. Innerhalb einer Schemaversion können neue optionale Schlüssel dazukommen.
- Verlass dich nicht auf die Reihenfolge der Schlüssel. Der Writer schreibt sie in stabiler Reihenfolge, weil das Diffs lesbar macht — aber YAML-Mappings sind ungeordnet, und ein konformes Leseprogramm muss sie so behandeln.
- Unterscheide „fehlt“ von „null“.
placefehlt, wenn es keinen Ort gibt.titleist vorhanden und null, wenn es keinen Titel gibt. Dieser Unterschied ist beabsichtigt und bleibt so.
Versionierung und Deprecation
ortszeit_schema ist eine ganze Zahl. Kein Datum und keine semantische Version.
Was die Version nicht erhöht: ein neuer optionaler Schlüssel, ein neuer erlaubter Wert in einem Feld, das Leseprogramme ohnehin generisch behandeln müssen, oder ein Wert, der vorher immer gefehlt hat. Leseprogramme, die sich an Regel 1 halten, sind davon nicht betroffen.
Was die Version erhöht: ein Schlüssel verschwindet, wird umbenannt, ändert seinen Typ, oder die Bedeutung eines bestehenden Werts ändert sich.
Das Versprechen, das an einer Erhöhung hängt:
- Die neue Version steht auf dieser Seite, bevor irgendetwas sie schreibt.
- Die vorherige Version bleibt hier dokumentiert, als veraltet markiert, mit dem Datum, ab dem sie nicht mehr geschrieben wird.
- Der Importer von Ortszeit akzeptiert die vorherige Version mindestens zwölf Monate über dieses Datum hinaus, und Version 1 praktisch unbegrenzt — ein altes Archiv wiederherstellen zu können ist der ganze Sinn des Formats.
- Eine Versionserhöhung wird mit dem Release angekündigt, das sie bringt, nicht danach.
Was das Format nicht enthält
Deutlich gesagt, denn eine dokumentierte Lücke ist eine Einschränkung, eine undokumentierte eine böse Überraschung:
- Passwörter von Reisen, siehe oben.
- Medien, die an keinem Beitrag hängen. Der Aufbau hat keinen Platz dafür, also werden sie nicht exportiert. Eine bekannte Lücke.
- Das PDF pro Reise, das bei bezahlten Exporten zugesagt ist. Es ist spezifiziert, aber noch nicht gebaut — und es wäre ohnehin nicht Teil dieses Baums: Es ist eine Darstellung des Inhalts, nicht der Inhalt.
- Konto-, Abo- und Kontingentstatus. Nichts davon ist deins zum Mitnehmen, alles davon ist abgeleitet.