Zum Inhalt

Lookups — Auswahllisten und Vorbelegung

Lookups sind das Mittel, mit dem ein Feld seine Werte aus einer Quelle zieht: aus einer festen Liste, einer Datenbank-Tabelle, freiem SQL, einem Skript oder einem LDAP-Verzeichnis. Dazu gehören:

Schlüssel Wofür?
kc:lookupOptions feste Auswahlliste direkt im Schema
kc:lookup Auswahlliste aus Datenbank, externer Datenquelle, SQL, Skript oder LDAP
kc:lookupDisplay wie der gewählte Wert in Druck und Listen erscheint
kc:valueLookup einen Wert aus der Datenbank nachziehen
kc:defaultLookup Vorbelegung neuer Datensätze per SQL
kc:defaultExpression Vorbelegung neuer Datensätze per JavaScript

Wer Datensätze mit mehreren Feldern aus einer Suche übernehmen will, nimmt einen Picker. Und wer in EINEM Feld mehrere Werte unterbringen will — etwa eine Länderauswahl —, findet die Eingabearten dafür unter Mehrere Werte in einem Feld.

Zusammenklicken reicht — das JSON ist zum Nachschauen da

Auswahllisten richtest du im Formular-Editor ein. Eine feste Werteliste, eine Liste aus einer Datenbank-Tabelle und eine Liste aus einem LDAP-Verzeichnis klickst du vollständig zusammen, ohne eine Zeile zu tippen. Nur die beiden freien Quellen — eigenes SQL und Skript — tippst du selbst; auch dafür hat der Editor ein eigenes Eingabefeld samt Vollbild-Ansicht.

Die eingeklappten Kästen „Hinter den Kulissen" auf dieser Seite zeigen, was der Editor dabei in das Formular schreibt. Aufklappen lohnt sich, wenn du ein Formular exportieren und mitnehmen willst oder wissen möchtest, warum eine Liste leer bleibt — zum Arbeiten brauchst du es nicht.

Im Formular-Editor

Im Eigenschaften-Dialog eines Feldes, Reiter Auswahl, wählst du den Auswahl-Typ:

Auswahl-Typ Ergebnis im Schema
Freie Eingabe kein Vorschlag
Feste Werteliste kc:lookupOptions als einfache Liste (z. B. 2026, 2027)
Liste mit Code + Text kc:lookupOptions mit key/value — gespeichert wird der Code
Aus Datenbank / Verzeichnis kc:lookup — Tabelle, SQL, Skript oder LDAP
Such-Dialog kc:picker — siehe Picker

Darunter stehen die Schalter, die für alle Listen gelten:

Schalter Schlüssel Wirkung
Lookup aktiv (aus / ein / bedingt) kc:lookupActiveIf Liste nur unter einer Bedingung anbieten
Nur Werte aus Liste kc:lookupStrict Anwender muss einen Listeneintrag wählen
Beim Betreten öffnen kc:lookupOpenOnFocus Liste klappt beim Hineinklicken auf
Gewählten Wert nicht erneut anbieten kc:hideSelectedOption gewählten Eintrag in der Liste ausblenden
Anzeige kc:lookupDisplay siehe unten

Werteliste als Vorschlag

Eine Feste Werteliste ohne Nur Werte aus Liste wird als Textfeld mit Vorschlägen dargestellt — die Liste hilft, erlaubt aber eigene Eingaben. Mit dem Schalter wird daraus ein echtes Auswahlfeld, und auch beim Speichern prüft KCForms, dass der Wert aus der Liste stammt. Bei nachgeladenen Listen (Datenbank, LDAP) und bei „Lookup aktiv: bedingt“ prüft nur die Oberfläche.

Auswahl aus einer festen Werteliste

Die häufigste Liste tippst du direkt im Editor ein. Es gibt sie in zwei Bauarten: als reine Textliste — oder als Liste mit Code + Text, bei der der Anwender den Klartext sieht („Frau"), gespeichert aber ein kurzer Code wird („FRAU"). Die zweite Bauart lohnt sich immer dann, wenn der Text später einmal anders lauten könnte, die gespeicherten Daten aber stabil bleiben sollen.

Hinter den Kulissen: feste Werteliste mit Code und Klartext
"anrede": {
  "type": "string", "title": "Anrede",
  "kc:lookupOptions": [
    { "key": "FRAU",   "value": "Frau" },
    { "key": "HERR",   "value": "Herr" },
    { "key": "DIVERS", "value": "Divers" }
  ]
}

key wird gespeichert, value angezeigt.

Die reine Textliste ist noch kürzer — sie besteht nur aus den Texten selbst: "kc:lookupOptions": ["2026", "2027", "2028"].

Mit "kc:widget": "radio" erscheint die Liste als Radio-Gruppe (kc:radioColumns für mehrere Spalten, kc:allowDeselect: true erlaubt das Abwählen).

Auswahl aus Datenbank, Skript oder Verzeichnis

kc:lookup kennt fünf Quellen. Sie schließen sich gegenseitig aus.

1. Aus einer Datenbank-Tabelle

Der Normalfall: Du wählst im Editor die Tabelle, die Spalte mit dem zu speichernden Schlüssel und die Spalte mit dem Anzeigetext. Dazu kommen nach Bedarf eine Sortierung, ein fester Filter, eine Obergrenze für die Zahl der Einträge und — falls die Daten gar nicht in der KCForms-Datenbank liegen — eine externe Datenquelle.

Hinter den Kulissen: Auswahlliste aus einer Datenbank-Tabelle
"land": {
  "type": "string", "title": "Land",
  "kc:lookup": {
    "table": "country",
    "keyColumn": "code",
    "valueColumn": "label",
    "orderBy": "label ASC",
    "limit": 500
  }
}
Schlüssel Standard Bedeutung
table (Pflicht) Tabellenname
keyColumn — Spalte, deren Wert gespeichert wird
valueColumn = keyColumn Spalte für den Anzeigetext
displayColumns — mehrere Spalten als Anzeigetext, siehe unten
separator , Trennzeichen zwischen den displayColumns
column — Kurzform, wenn Schlüssel und Anzeige gleich sind
where — Bedingung, auch mit ${feld}-Platzhaltern
orderBy — Sortierung, z. B. label oder label DESC
distinct false doppelte Werte zusammenfassen
limit 1000 höchstens so viele Einträge (1–10 000)
dataSourceId — statt der KCForms-Datenbank eine externe Datenquelle abfragen

Tabellen- und Spaltennamen müssen gültige SQL-Bezeichner sein.

Anzeigetext aus mehreren Spalten

Soll im Aufklappfeld „Nachname, Vorname" stehen, wähle im Editor mehrere Anzeige-Spalten aus. Die Reihenfolge deiner Auswahl ist die Reihenfolge im Text; das Trennzeichen legst du daneben fest.

"besteller_id": {
  "type": "string", "title": "Besteller",
  "kc:lookup": {
    "table": "USERS",
    "keyColumn": "ID_USER",
    "displayColumns": ["LAST_NAME", "FIRST_NAME"],
    "separator": ", ",
    "orderBy": "LAST_NAME, FIRST_NAME"
  }
}

Warum nicht einfach selbst CONCAT schreiben?

Weil es keine Schreibweise gibt, die auf allen vier unterstützten Datenbanken funktioniert — gemessen am 28.09.2026:

Schreibweise MySQL PostgreSQL Oracle SQL Server
CONCAT(a, b, c) ✔ ✔ ✘ ✔
CONCAT(CONCAT(a, b), c) ✔ ✔ ✔ ✔
a || b || c liefert 0 ✔ ✔ ✘
a + b + c liefert 0.0 ✘ ✘ ✔

Oracles CONCAT nimmt genau zwei Argumente; beliebig viele verkettet man dort mit ||. Nur ist || auf SQL Server ein Syntaxfehler und auf MySQL das logische ODER — dort kommt ohne jede Fehlermeldung eine Spalte voller Nullen heraus. Mit displayColumns baut KCForms den Ausdruck selbst, passend zur laufenden Datenbank, und hüllt jede Spalte in COALESCE(…, '') — sonst fiele auf MySQL die ganze Zeile aus der Liste, sobald eine der Spalten leer ist.

Brauchst du mehr als eine Verkettung (Join, CASE), nimm freies SQL — dann gilt allerdings die Tabelle oben, und die Abfrage ist an die Datenbank gebunden, für die du sie geschrieben hast.

2. Mit einer eigenen SQL-Abfrage

Für Joins, CASE oder COALESCE reicht die Tabellenauswahl nicht — dann schreibst du die Abfrage selbst. Die erste Spalte des Ergebnisses ist der gespeicherte Wert, die zweite (falls vorhanden) der Anzeigetext. Das Beispiel unten baut so eine Ansprechpartner-Liste aus zwei Tabellen zusammen und zeigt Vor- und Nachnamen in einer Zeile.

Hinter den Kulissen: Auswahlliste aus freiem SQL
"kontaktperson": {
  "type": "string", "title": "Ansprechpartner",
  "kc:lookup": {
    "sql": "SELECT k.id_user, CONCAT(u.FIRST_NAME, ' ', u.LAST_NAME) FROM kontakt k JOIN USERS u ON k.id_user = u.ID_USER WHERE k.kunde_id = ${kundeId} ORDER BY u.LAST_NAME"
  }
}

Auch hier wirkt dataSourceId.

Freies SQL bindet dich an eine Datenbank

Das Beispiel oben benutzt CONCAT mit drei Argumenten — das läuft auf MySQL, PostgreSQL und SQL Server, aber nicht auf Oracle. Wenn dein Formular auf mehreren Datenbanken laufen soll, nimm für reine Spalten-Verkettungen displayColumns (siehe oben) statt freiem SQL.

Zwei Sicherungen: Die Abfrage muss mit SELECT oder WITH beginnen, und Befehle wie INSERT, UPDATE, DELETE, DROP oder ALTER werden abgelehnt.

3. Aus einem Skript

Wenn sich die Einträge weder als Tabelle noch als SQL-Abfrage beschreiben lassen, baust du die Liste mit voller JavaScript-Logik selbst zusammen und meldest jeden Eintrag einzeln an. Die Quelle heißt dann script.

Hinter den Kulissen: Auswahlliste aus einem Skript
"landDropdown": {
  "type": "string", "title": "Land",
  "kc:lookup": {
    "script": "db.query('SELECT code, label FROM country').forEach(function (r) { handle({ key: r.code, value: r.label }); });"
  }
}

Im Skript stehen handle({ key, value }) (fügt einen Eintrag hinzu), db, log, kc und declaration.elements zur Verfügung. Skript-Lookups dürfen mindestens 30 Sekunden laufen — länger als normale Hooks.

4. Aus einem LDAP-Verzeichnis

Werte aus einem Verzeichnis, z. B. für eine Benutzerauswahl: Du wählst den Konnektor und legst fest, ab welchem Ast gesucht wird, welcher Filter gilt und welches Attribut gespeichert bzw. angezeigt wird.

Hinter den Kulissen: Auswahlliste aus einem LDAP-Verzeichnis
"sachbearbeiter": {
  "type": "string", "title": "Sachbearbeiter",
  "kc:lookup": {
    "ldapConnector": "kcads",
    "ldapBase":   "ou=Users,dc=kcforms,dc=test",
    "ldapFilter": "(objectClass=inetOrgPerson)",
    "keyAttr":    "uid",
    "valueAttr":  "cn"
  }
}
Schlüssel Bedeutung
ldapConnector Name des LDAP-Konnektors (Pflicht)
ldapBase Suchbasis; ohne Angabe die Basis des Konnektors
ldapFilter LDAP-Filter; Standard (objectClass=*)
keyAttr gespeichertes Attribut (Pflicht), auch dn
valueAttr angezeigtes Attribut; Standard = keyAttr

5. Feste Liste als Quelle

kc:lookup akzeptiert auch eine Liste unter options (gleiches Format wie kc:lookupOptions). Für neue Formulare ist kc:lookupOptions der übliche Weg — so schreibt es auch der Formular-Editor.

Platzhalter

where und sql verstehen Platzhalter, die sicher als Parameter an die Datenbank gehen (keine SQL-Injection):

Platzhalter Quelle
${feldname} aktueller Wert des Feldes — für abhängige Listen
${user.name} Login-Name des angemeldeten Benutzers
${user.idUser} Benutzer-ID
${user.firstName}, ${user.lastName}, ${user.email} Name und E-Mail
${user.locale}, ${user.roles} Sprache, Rollen

Die ${user.…}-Werte stammen immer vom Server, beim Öffnen des Formulars ebenso wie beim Nachladen einer Liste. Ein Filter wie LOGIN_NAME = ${user.name} lässt sich also nicht vom Browser aus auf einen anderen Benutzer umbiegen. Deshalb ist ein Formularfeld namens user in Lookup-Platzhaltern nicht erreichbar.

Damit entstehen abhängige Listen: Das Bundesland-Feld bietet nur die Bundesländer des Landes an, das im Feld land steht — im Filter steht dafür der Platzhalter ${land} statt eines festen Werts.

Hinter den Kulissen: Bundesland-Liste, gefiltert über das Feld land
"bundesland": {
  "type": "string", "title": "Bundesland",
  "kc:lookup": {
    "table": "country_state",
    "keyColumn": "code", "valueColumn": "label",
    "where": "country_code = ${land}"
  }
}

Ist land leer, wird der Platzhalter zu NULL — country_code = NULL trifft nichts, die Liste bleibt leer. Genau so gewollt.

Abhängige Listen

Enthält ein Lookup einen ${feld}-Platzhalter, merkt sich KCForms die Abhängigkeit (kc:lookupDependsOn, wird automatisch gesetzt). Ändert sich feld, lädt das Formular die abhängige Liste neu. Passt der bisher gewählte Wert nicht mehr zur neuen Liste, wird er geleert. ${user.…}-Platzhalter lösen kein Neuladen aus.

Wie der gewählte Wert später erscheint

Der gespeicherte Schlüssel ist oft ein Code ("DE"), angezeigt werden soll der Klartext. kc:lookupDisplay steuert die Darstellung in Druck und Listen:

Wert Darstellung
(leer) — Auto Klartext, wenn Schlüssel und Klartext gleich sind; sonst beides
value nur der Klartext — „Deutschland"
key-value beides — „DE — Deutschland"
key nur der Schlüssel — „DE"

Einen einzelnen Wert nachziehen

Manchmal braucht es keine Liste, sondern einen Wert aus einer anderen Tabelle. Beispiel: Der Anwender tippt eine Lieferantennummer, ein Feld daneben zeigt den Namen. Dafür gibt es kc:valueLookup. Eingestellt wird dasselbe wie bei einer Liste — Tabelle, Schlüssel- und Anzeigespalte — plus ein Filter, der die passende Zeile findet.

Hinter den Kulissen: Lieferantenname zur eingetippten Nummer
"lieferantennummer": { "type": "string", "title": "Lieferanten-Nr." },
"lieferantenName": {
  "type": "string", "title": "Lieferant", "kc:readonly": true,
  "kc:valueLookup": {
    "table": "vendor",
    "keyColumn": "vendor_no",
    "valueColumn": "name",
    "where": "vendor_no = ${lieferantennummer}"
  }
}

Ändert sich lieferantennummer, holt das Formular den Wert nach und schreibt ihn in lieferantenName; ohne Treffer bleibt das Feld leer. Es gelten dieselben Quellen wie bei kc:lookup — genommen wird der Anzeigewert des ersten Treffers.

Vorbelegung neuer Datensätze

Im Formular-Editor: Eigenschaften-Dialog, Reiter Erweitert → Vorbelegung mit den Varianten Wert (kc:defaultValue), Skript (JS) (kc:defaultExpression) und SQL-Select (kc:defaultLookup).

Die Vorbelegung greift nur bei neuen Datensätzen und nur, wenn das Feld noch leer ist. onLoad läuft vorher — was ein Skript setzt, gewinnt. Reihenfolge: erst kc:defaultLookup, dann kc:defaultExpression als Rückfallstufe.

Vorbelegung per SQL-Select

Ein SELECT, das genau einen Wert liefert — typisch der ausgeschriebene Name des angemeldeten Benutzers, der beim Anlegen im Erfasser-Feld stehen soll.

Hinter den Kulissen: Erfassername aus der Benutzertabelle vorbelegen
"erfasserName": {
  "type": "string", "kc:readonly": true,
  "kc:defaultLookup": {
    "sql": "SELECT CONCAT(FIRST_NAME, ' ', LAST_NAME) FROM USERS WHERE LOGIN_NAME = ${user.name}"
  }
}

Gleiche Möglichkeiten wie kc:lookup (Tabelle, SQL, Skript, Datenquelle).

Vorbelegung per JavaScript-Ausdruck

Ein einzelner JavaScript-Ausdruck, dessen Ergebnis in das Feld wandert — etwa das aktuelle Datum mit Uhrzeit oder eine Begrüßung, die den Vornamen des angemeldeten Anwenders einsetzt (und ohne Vornamen auf ein schlichtes „Hallo" zurückfällt).

Hinter den Kulissen: Datum und Begrüßung vorbelegen
"datum": {
  "type": "string", "kc:readonly": true,
  "kc:defaultExpression": "new Date().format('dd.MM.yyyy HH:mm')"
},
"begruessung": {
  "type": "string",
  "kc:defaultExpression": "ctx.user.firstName ? 'Hallo, ' + ctx.user.firstName : 'Hallo'"
}

Der Ausdruck hat dieselben Möglichkeiten wie ein onLoad-Skript (ctx, db, log, kc, fs, xml, mail, eingebundene Bibliotheken). Ein Semikolon am Ende schadet nicht. Fehler werden protokolliert, das Formular öffnet trotzdem.

Fehlerbilder

Symptom Ursache
Liste leer ein ${feld}-Platzhalter zeigt auf ein leeres Feld — oder die Quelle meldet einen Fehler (Backend-Log)
Fehler zu Tabellen- oder Spaltennamen Namen mit unzulässigen Zeichen
SQL wird abgelehnt sql beginnt nicht mit SELECT/WITH oder enthält INSERT/UPDATE/…
Skript-Lookup bricht ab Skript braucht länger als 30 Sekunden — vereinfachen oder als Picker bauen
Vorbelegung greift nicht onLoad setzt das Feld bereits, oder es ist kein neuer Datensatz

Vollständiges Beispiel: Land → Bundesland

Zum Abschluss beide Felder zusammen in einem Abschnitt „Adresse": ein Land-Feld mit alphabetisch sortierter Länderliste und daneben ein Bundesland-Feld, das über ${land} an der Auswahl im Land-Feld hängt. Beide sind Pflichtfelder.

Hinter den Kulissen: Abschnitt „Adresse" mit zwei verketteten Listen
{
  "type": "object",
  "title": "Adresse",
  "kc:layout": "horizontal-2",
  "properties": {
    "land": {
      "type": "string", "title": "Land",
      "kc:lookup": { "table": "country", "keyColumn": "code",
                     "valueColumn": "label", "orderBy": "label" }
    },
    "bundesland": {
      "type": "string", "title": "Bundesland",
      "kc:lookup": { "table": "country_state", "keyColumn": "code",
                     "valueColumn": "label", "where": "country_code = ${land}",
                     "orderBy": "label" }
    }
  },
  "required": ["land", "bundesland"]
}

Wechselt der Anwender das Land, lädt das Bundesland-Feld neu — du beschreibst nur die Abhängigkeit im where.