Sammlungen anlegen
Eine Sammlung (Collection) ist eine Tabelle deines Projekts.
Du legst sie als YAML-Datei in api/collections/ an.
Der Dateiname ist frei, entscheidend ist das Feld name.
Beispiel-Sammlungen zum Abschauen: content, navigation, medialib, tag, comment.
Ordnerapi/
Ordnercollections/
- content.yml
- navigation.yml
- medialib.yml
- tag.yml
- comment.yml
Minimal-Beispiel
Abschnitt betitelt „Minimal-Beispiel“Die kleinste Sammlung braucht name und permissions (beides Pflicht laut Schema) — fields ist dagegen optional.
Abgeschaut von tag.yml (vereinfacht):
# api/collections/notiz.yml# yaml-language-server: $schema=../../tibi-types/schemas/config/collection.schema.jsonname: notizuploadPath: ../media/notizaudit: enabled: truepermissions: public: methods: get: true user: methods: get: true post: true put: true delete: trueindexes: - name: name_unique key: - name unique: true background: truesearch: - name: default mode: regex fields: [name, text]fields: - name: name type: string meta: label: { de: "Name", en: "Name" } - name: text type: string meta: label: { de: "Text", en: "Text" }- Datei
api/collections/<name>.ymlanlegen, Schema-Zeile aus dem Beispiel kopieren. namekleinschreiben, keine Umlaute und keine Leerzeichen (content,tag,commentals Vorbild).permissionssetzen — sonst liest niemand die Daten (Details: Rechte und Tokens).uploadPathsetzen (../media/<name>wie in allen Starter-Sammlungen) — empfohlen auch ohne Datei-Felder: Datei-Uploads landen dann in einem eigenen Verzeichnis pro Sammlung, und der Server räumt beim Löschen gezielt auf. Das Schema verlangt es nicht (requiredsind nurnameundpermissions), der Starter pflegt es aber überall.- Projekt neu laden, damit der tibi-server die Datei einliest.
- Testen:
POSTanlegen, doppelten Wert schicken →409bei Unique-Index.
Meta: Label, Gruppe, Icon, Vorschau
Abschnitt betitelt „Meta: Label, Gruppe, Icon, Vorschau“meta steuert, wie tibi-admin die Sammlung anzeigt.
Pflicht ist nichts davon — aber ohne label rätst du im Admin.
| Schlüssel | Wirkung | Beispiel aus dem Starter |
|---|---|---|
label |
Anzeigename (de/en) |
tag.yml: { de: "Schlagwörter", en: "Tags" } |
group |
Gruppierung in der Navigation | content (content, medialib, navigation, tag), community (comment), system (ssr) |
muiIcon |
Icon im Admin | label, menu, chat, folder-multiple-image |
imageUrl |
Vorschaubild für Karten | Unsplash-URL in content.yml |
preview |
Listenvorschau: label, secondary, tertiary, image |
content.yml: label: name, secondary: path, tertiary: lang |
viewHint |
Darstellungsart im Admin | siehe unten |
sidebar |
Eigene Seitenleiste mit Gruppen | content.yml: publishing, seo |
i18n.entry |
Übersetzungs-Kopplung | content.yml: languageField: lang, groupField: translationKey |
singleton |
Genau ein Eintrag (enabled, optional key) |
siehe unten |
sections |
Formular-Abschnitte (group, label, collapsible, collapsed) |
siehe unten |
query |
Deklarierte Zusatz-Querykeys für Hooks | siehe unten |
agent |
Zweck, Hinweise und Workflows für Editor-Agenten | siehe unten |
defaultSort.order |
Sortierung, inkl. MANUALLY (manuelle Reihenfolge per Drag) |
siehe unten |
Felder positionierst du mit meta.position: main, sidebar:<gruppe> oder sidebar:settings.
Beispiel: lang liegt in content.yml auf sidebar:settings, active auf sidebar:publishing.
viewHint aus dem Starter (geprüft, alle drei Varianten):
viewHint: cards— Kartenansicht (content.yml).viewHint.media— Medienansicht mit Upload und KI-Alt-Text (medialib.yml).viewHint.navigation— Baumansicht (navigation.yml, Bäume: Header/Footer je Sprache):nodesFieldnennt dasobject[]-Unterfeld mit den Kind-Knoten (fällt es weg, gilt der Feldname selbst —NavigationTreeField.svelte:106),declaredTreeslistet die Bäume (je Eintrag Pflicht:singleton, optionallabel,maxLevel).
Singleton, Sections, Query, Agent, DefaultSort
Abschnitt betitelt „Singleton, Sections, Query, Agent, DefaultSort“| Schlüssel | Wirkung | YAML-Beispiel |
|---|---|---|
meta.singleton.enabled: true (+ optional key) |
Genau ein Eintrag pro Sammlung (bzw. pro key) |
singleton: { enabled: true } |
meta.sections[] |
Formular-Abschnitte; group ist Pflicht, dazu label (de/en), collapsible, collapsed |
sections: [{ group: seo, label: { de: "SEO", en: "SEO" }, collapsible: true }] |
meta.query.<name> |
Deklariert Zusatz-Querykeys, die Hooks per ctx.request().query(name) lesen (type: string/number/boolean, optional enum, maxLength, description) |
query: { preview: { type: boolean } } |
meta.agent |
Freiform-Doku für Editor und Agenten (purpose, instructions, fieldHints, workflows, dazu examples, hints); wird u. a. über MCP/REST _meta ausgespielt |
agent: { purpose: "Seiteninhalte", workflows: ["Seite anlegen → übersetzen"] } |
meta.defaultSort |
Start-Sortierung (field + order: ASC, DESC oder MANUALLY — manuell heißt Drag-Reihenfolge in der Liste) |
defaultSort: { field: name, order: MANUALLY } |
Indizes und Suche
Abschnitt betitelt „Indizes und Suche“Indizes
Abschnitt betitelt „Indizes“Jeder Eintrag in indexes braucht nur key (name ist freiwillig).
key ist eine Liste von Feldnamen, Text-Index mit "$text:$**".
# aus content.yml — Text-Index plus zwei Unique-Indizesindexes: - name: content_text key: - "$text:$**" defaultLanguage: none background: true - name: path_lang key: - path - lang unique: true background: true - name: translation key: - translationKey - lang unique: true background: trueunique: true schützt vor Doppelten: der zweite POST mit gleichem path + lang scheitert — modern mit 409, im Legacy-Default (Standard) mit 500 (siehe Fehlerbilder).
So erzwingt der Starter auch einzelne Navigationsbäume (type_language in navigation.yml) und einzelne Tag-Namen (name_unique in tag.yml).
Jeder Eintrag in search braucht nur name und mode (fields ist freiwillig).
Vier Modi kommen im Starter vor:
| Modus | Wofür | Beispiel |
|---|---|---|
regex |
Einfache Teilstringsuche | tag.yml, navigation.yml, comment.yml: default |
ngram |
Fehlertolerante Fuzzy-Suche | content.yml: fuzzy mit autoRegenerate: true |
vector |
Semantische Vektorsuche | content.yml: semantic mit vector: { provider: bge_m3 } |
combined |
Hybrid aus semantisch + fuzzy (RRF) | content.yml: combined mit rrf: { k: 60, topK: 100 } |
Abfrage dazu: GET …/content?q=…&qName=combined.
Details stehen unter Lesen und suchen.
Häufige Fehler
Abschnitt betitelt „Häufige Fehler“| Fehlerbild | Ursache | Lösung |
|---|---|---|
409 beim Anlegen |
unique-Index verletzt (path+lang, translationKey+lang, Tag-name) |
Vor POST per filter prüfen, ob der Datensatz schon existiert |
project not found |
Projekt nicht registriert oder Namespace falsch | Siehe Neues Projekt Schritt 5 |
| Validierung schlägt fehl | Pflichtfeld fehlt oder pattern verletzt (z. B. path ohne führenden /) |
Feldregeln stehen unter Felder und Widgets |
| Öffentlich leere Liste, mit Token voll | active:true fehlt — der filter_public-Hook filtert alles weg |
Siehe Rechte und Tokens |
| Datei-Upload landet falsch | uploadPath fehlt oder zeigt ins falsche Verzeichnis |
../media/<name> wie in allen Starter-Sammlungen setzen |
Verifiziert am e3f92eb 2026-10-17 (tibi-svelte-starter), am 4c55f82 2026-10-17 (tibi-types); Meta-Ergänzungen verifiziert am 1897bb5 2026-10-17 (tibi-docs), am 2b091d8 2026-10-17 (tibi-admin-nova: NavigationTreeField), am 4c55f82 2026-10-17 (tibi-types: collection-meta.schema.json)