Zum Inhalt springen

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

Die kleinste Sammlung braucht name und permissions (beides Pflicht laut Schema) — fields ist dagegen optional. Abgeschaut von tag.yml (vereinfacht):

api/collections/notiz.yml — Minimal-Beispiel (vereinfacht aus tag.yml)
# api/collections/notiz.yml
# yaml-language-server: $schema=../../tibi-types/schemas/config/collection.schema.json
name: notiz
uploadPath: ../media/notiz
audit:
enabled: true
permissions:
public:
methods:
get: true
user:
methods:
get: true
post: true
put: true
delete: true
indexes:
- name: name_unique
key:
- name
unique: true
background: true
search:
- 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" }
  1. Datei api/collections/<name>.yml anlegen, Schema-Zeile aus dem Beispiel kopieren.
  2. name kleinschreiben, keine Umlaute und keine Leerzeichen (content, tag, comment als Vorbild).
  3. permissions setzen — sonst liest niemand die Daten (Details: Rechte und Tokens).
  4. uploadPath setzen (../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 (required sind nur name und permissions), der Starter pflegt es aber überall.
  5. Projekt neu laden, damit der tibi-server die Datei einliest.
  6. Testen: POST anlegen, doppelten Wert schicken → 409 bei Unique-Index.

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): nodesField nennt das object[]-Unterfeld mit den Kind-Knoten (fällt es weg, gilt der Feldname selbst — NavigationTreeField.svelte:106), declaredTrees listet die Bäume (je Eintrag Pflicht: singleton, optional label, maxLevel).
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 }

Jeder Eintrag in indexes braucht nur key (name ist freiwillig). key ist eine Liste von Feldnamen, Text-Index mit "$text:$**".

content.yml — Text-Index plus zwei Unique-Indizes
# aus content.yml — Text-Index plus zwei Unique-Indizes
indexes:
- 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: true

unique: 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.

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)