Widgets konfigurieren
Den Widget-Katalog lesen: configSchema als JSON-Schema, Pflichtfelder, Beispiele — und je eingebautem Baustein eine fertige Widget-Instanz.
9 Minuten LesezeitApps-SDKStand: 25.10.2026
Auf dieser Seite
- Den Katalog abrufen
- Was im configSchema steht
- Vier Regeln, die man dem Schema nicht ansieht
- Zwei Werte mit fester Form: Ort und Fläche
- Die sechs eingebauten Bausteine
- Je Baustein eine fertige Widget-Instanz
- Eine Konfiguration prüfen, bevor Sie sie schreiben
- Das Formular im Editor
- Die Rangfolge
- Eigene Felder: registerConfigField
- Feldarten mit eigenem Bedienelement
- Schnellfelder: quickFields
- Der Passt-Bericht: Schlüssel sind Feldnamen
- Weiterführendes
Bevor Sie loslegen
- Für wen
- Wer Szenen über die Schnittstelle anlegt oder ändert — ein eigenes Werkzeug, ein Assistent, ein Import.
- Voraussetzung
- ein Ausweis Ihrer Organisation für
GET /v1/widget-catalog; ein JSON-Schema-Prüfer, der Draft 2020-12 versteht.
Der Widget-Katalog liefert zu jedem Widget ein vollständiges JSON-Schema seiner Konfiguration: jedes Feld mit Überschrift und Erklärung, die Pflichtfelder benannt und mindestens eine fertige Beispiel-Konfiguration. Wer eine Szene ohne den Editor baut, liest daraus die Regeln, statt sie zu raten — und prüft seine Konfiguration dagegen, bevor er sie schreibt.
Den Katalog abrufen
curl -sS "$SZENERA_API/v1/widget-catalog" \
-H "Authorization: Bearer $TOKEN" | jq '.entries | length'
Die Antwort trägt unter entries je Widget einen Eintrag:
| Feld | Bedeutung |
|---|---|
appId |
Die App, zu der das Widget gehört — oder core bei den sechs eingebauten Bausteinen. |
version |
Die Fassung der App, aus der die Definition stammt; bei den eingebauten Bausteinen fest 0.0.0. |
definition.id |
Der Widget-Typ. Genau dieser Wert steht später in widgetType der Widget-Instanz. |
definition.name, definition.description |
Name und Kurztext, wie sie auch die Auswahl im Editor zeigt. |
definition.configSchema |
Das JSON-Schema der Konfiguration — der Gegenstand dieser Seite. |
definition.size.min |
Der kleinste Rahmen in Bezugspixeln einer 1920 × 1080-Szene, in dem das Widget noch etwas Sinnvolles zeigt. |
definition.preview.defaultConfig |
Die Konfiguration, mit der eine frisch eingefügte Instanz startet. |
definition.preview.variants |
Die Darstellungen, die das Widget beherrscht, je mit einer Teil-Konfiguration und, als Vorlage, mit einer Startgröße (size). |
definition.preview.states |
Benannte Beispiel-Nutzlasten für die Vorschau im Editor (Regen, Nacht, keine Daten). Für eine Konfiguration ohne Bedeutung. |
Der Katalog spiegelt den jeweils neuesten freigegebenen Stand einer App. Eine Organisation, die eine ältere Fassung installiert hat, kann geringfügig davon abweichen; maßgeblich ist dann die Fassung, die in appVersionPin der Instanz steht.
Was im configSchema steht
Das configSchema ist ein JSON-Schema-Dokument nach Draft 2020-12. Es beschreibt genau das Objekt, das später als config in der Widget-Instanz steht.
| Schlüssel | Bedeutung |
|---|---|
$schema |
Die Fassung der Spezifikation: https://json-schema.org/draft/2020-12/schema. |
type |
Immer object — eine Widget-Konfiguration ist nie eine Liste und nie ein blanker Wert. |
title, description |
Name und Kurztext des Widgets, damit das Dokument auch für sich allein verständlich bleibt. |
properties |
Die Felder der Konfiguration, je Feld ein eigener Knoten. |
required |
Die Feldnamen, ohne die die Konfiguration unvollständig ist. Häufig leer: Felder mit Vorgabe sind nicht Pflicht. |
examples |
Mindestens eine vollständige Konfiguration, die Sie unverändert übernehmen können. |
Jeder Knoten unter properties trägt Klartext und, wo es sie gibt, die Grenzen des Werts:
| Schlüssel | Bedeutung |
|---|---|
title |
Kurze Überschrift des Feldes, wie sie auch im Editor über dem Bedienelement steht. |
description |
Ein Satz dazu, was das Feld bewirkt. |
type |
string, number, integer, boolean, array oder object. |
enum |
Die zulässigen Werte eines Auswahlfeldes — andere weist die Prüfung ab. |
default |
Der Wert, der gilt, wenn das Feld fehlt. |
minimum, maximum, minLength, maxLength, pattern |
Die Grenzen des Werts; pattern steht meist bei Farben (#rrggbb) und Kennungen. |
properties, items |
Bei verschachtelten Objekten und Listen: dieselben Regeln eine Ebene tiefer. |
Ein Ausschnitt aus dem Baustein „Text“:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"title": "Text",
"description": "Statischer Text-Block mit voller Kontrolle über Schrift, Ausrichtung und Farben.",
"properties": {
"content": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"title": "Text",
"description": "Der angezeigte Text. Zeilenumbrüche bleiben erhalten; Markup gibt es hier nicht."
},
"textAlign": {
"type": "string",
"enum": ["left", "center", "right"],
"default": "center",
"title": "Ausrichtung",
"description": "Waagerechte Ausrichtung des Textes."
}
},
"required": ["content"],
"examples": [{ "content": "Ihr Text", "textAlign": "center", "fontSizeRem": 2 }]
}
Vier Regeln, die man dem Schema nicht ansieht
- Pflicht heißt: es gibt keine Vorgabe. In
requiredsteht nur, was die Plattform nicht selbst einsetzen kann — meistens der Inhalt. Alles andere trägt eindefault, und ein weggelassenes Feld nimmt genau diesen Wert an. - Unbekannte Felder werden verworfen, nicht abgelehnt. Ein Feld, das das Schema nicht kennt, macht eine Konfiguration nicht ungültig; es verschwindet beim Speichern. Das hält Szenen lesbar, die vor einem Feld-Umbau entstanden sind — verlässt sich aber niemand darauf, dass ein Tippfehler auffällt.
- Die Beispiele sind vollständig und geprüft. Jeder Eintrag in
examplesist eine Konfiguration, die das Schema erfüllt: die Start-Konfiguration und je Vorschau-Variante eine weitere. Der Bau einer App bricht ab, wenn ein Beispiel seine eigene Regel bricht. - Eine Konfiguration überlebt neue Fassungen. Eine App fügt Felder nur mit Vorgabe hinzu, und keines verschwindet oder ändert seinen Typ. Eine gespeicherte Konfiguration bleibt deshalb gültig, wenn die Organisation die App aktualisiert, und eine ältere Fassung, die ein Display noch spielt, liest auch eine Konfiguration mit Feldern einer neueren.
Zwei Werte mit fester Form: Ort und Fläche
Zwei Feldarten der Editor-Formulare schreiben Werte, deren Form überall gleich ist. Wer eine Konfiguration selbst baut, findet sie im configSchema als Objekt bzw. Zeichenkette wieder:
| Wert | Form | Bedeutung |
|---|---|---|
Ort (placeSelection()) |
{ "mode": "display" | "fixed", "place": Ort | null, "fallback": Ort | null }, ein Ort ist { "displayName", "countryCode", "latitude", "longitude" } |
display nimmt den Standort des Displays, das die Szene spielt, fixed den Ort unter place. fallback ist der Ersatzort für Displays, deren Standort keine bekannte Lage hat. Vorgabe: display, beide Orte null. |
| Fläche | eine Kennung wie light, dark, glass, transparent oder eine eigene der App |
Der Hintergrund des Widgets. Die zulässigen Werte nennt das Schema des Felds. |
Wie der Editor diese und alle anderen Felder zeigt, gliedert und an die Leiste am Widget stellt, beschreibt die Formular-Beschreibung der App (config-ui.ts, Typ WidgetConfigUI). Sie gehört zum Editor, nicht zum Katalog: Das configSchema bleibt die eine Regel für jeden, der eine Konfiguration schreibt.
Die sechs eingebauten Bausteine
Text, Bild, Video, Form, Grafik und Dokument gehören zur Grundausstattung: Sie werden nicht installiert, brauchen keine Zustimmung und stehen jeder Organisation offen. Im Katalog erscheinen sie wie die Widgets einer App — mit der reservierten Kennung core und dem festen Versions-Pin 0.0.0.
widgetType |
Name | Wofür |
|---|---|---|
text |
Text | Ein Text aus der Konfiguration, ohne Markup und ohne Netzwerkzugriff. |
image |
Bild | Ein Bild aus der Medienbibliothek, wahlweise mit langsamer Bildbewegung. |
video |
Video | Ein Video aus der Medienbibliothek, stumm und in Schleife. |
shape |
Form | Rechtecke, Kreise, Sterne, Pfeile und Linien — gezeichnet aus Zahlen. |
graphic |
Grafik | Ein Motiv aus der eingebauten Bibliothek, einfärbbar und spiegelbar. |
document |
Dokument | Ein PDF aus der Medienbibliothek als Seitenbilder: eine Seite, nacheinander oder zwei nebeneinander. |
Bild, Video und Dokument verweisen auf die Medienbibliothek statt auf eine Adresse: assetRef trägt die Kennung der Datei und die Fassungen, die die Anzeige braucht. Ohne assetRef zeigt das Widget einen gestalteten Platzhalter — das ist ein gültiger Zustand und kein Fehler.
Beim Dokument bleibt assetRef.variantSet leer: Seine Seitenbilder reisen immer mit, je Seite eine Zeile page-<n> in der Medien-Liste des Snapshots. Welche Seiten das Widget zeigt, steht in pages: numbers sind die gewählten Seiten, of die Seitenzahl der Datei beim Wählen. Fehlt pages, zeigt das Widget alle Seiten außer leeren. Hat die Datei später eine andere Seitenzahl als of, meldet die Prüfung der Szene DocumentPagesChanged (ein Hinweis, kein Halt). Das Dokument erklärt drei Vorlagen (page, spread, full) und die Mindestgröße 200 × 200.
Je Baustein eine fertige Widget-Instanz
So sieht ein Eintrag in document.widgets einer Szene aus. id ist eine neue Kennung im Format wgi_<ULID>, frame die Box in Pixeln der Szenenfläche, zIndex die Stapelreihenfolge.
Text
{
"id": "wgi_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"appId": "core",
"widgetType": "text",
"appVersionPin": "0.0.0",
"frame": { "x": 120, "y": 120, "w": 1680, "h": 400 },
"zIndex": 10,
"config": {
"content": "Willkommen in Ihrer Apotheke",
"fontFamily": "inter",
"fontStyle": "sans",
"fontWeight": "bold",
"fontSizeRem": 3,
"lineHeight": "tight",
"textTransform": "none",
"textAlign": "center",
"verticalAlign": "middle",
"padding": "md",
"textColor": "#0f172a",
"backgroundStyle": "transparent",
"backgroundColor": "#ffffff"
}
}
Bild
{
"id": "wgi_01ARZ3NDEKTSV4RRFFQ69G5FAW",
"appId": "core",
"widgetType": "image",
"appVersionPin": "0.0.0",
"frame": { "x": 0, "y": 0, "w": 1920, "h": 1080 },
"zIndex": 0,
"config": {
"assetRef": { "assetId": "ast_01ARZ3NDEKTSV4RRFFQ69G5FAV", "variantSet": ["webp", "original"] },
"fit": "cover",
"position": { "x": "center", "y": "center" },
"borderRadius": "none",
"backgroundMode": "color",
"backgroundColor": "#0f172a",
"motionEffect": "kenburns",
"kenBurns": { "zoom": 1.15, "durationSec": 24, "direction": "in" }
}
}
Video
{
"id": "wgi_01ARZ3NDEKTSV4RRFFQ69G5FAX",
"appId": "core",
"widgetType": "video",
"appVersionPin": "0.0.0",
"frame": { "x": 0, "y": 0, "w": 1920, "h": 1080 },
"zIndex": 0,
"config": {
"assetRef": { "assetId": "ast_01ARZ3NDEKTSV4RRFFQ69G5FAV", "variantSet": ["original"] },
"loop": true,
"muted": true,
"autoplay": true,
"fit": "cover",
"position": { "x": "center", "y": "center" },
"borderRadius": "none",
"backgroundMode": "color",
"backgroundColor": "#000000",
"startAtSeconds": 0
}
}
Form
{
"id": "wgi_01ARZ3NDEKTSV4RRFFQ69G5FAY",
"appId": "core",
"widgetType": "shape",
"appVersionPin": "0.0.0",
"frame": { "x": 120, "y": 560, "w": 480, "h": 80 },
"zIndex": 5,
"config": {
"variant": "rectangle",
"fillColor": "#0f766e",
"fillTransparent": false,
"fillOpacity": 1,
"strokeColor": "#0f172a",
"strokeWidth": 0,
"cornerRadius": 0,
"innerRadius": 60,
"pointDepth": 60,
"headSize": 36,
"shaftWidth": 28,
"lineCap": "butt",
"dash": "none",
"rotationDeg": 0
}
}
Grafik
{
"id": "wgi_01ARZ3NDEKTSV4RRFFQ69G5FAZ",
"appId": "core",
"widgetType": "graphic",
"appVersionPin": "0.0.0",
"frame": { "x": 1500, "y": 760, "w": 320, "h": 320 },
"zIndex": 20,
"config": {
"graphicId": "pfeil-rechts",
"tintColor": "#0f172a",
"accentColor": "#0f766e",
"flipH": false,
"flipV": false,
"opacity": 1
}
}
Dokument
{
"id": "wgi_01ARZ3NDEKTSV4RRFFQ69G5FB0",
"appId": "core",
"widgetType": "document",
"appVersionPin": "0.0.0",
"frame": { "x": 0, "y": 0, "w": 1920, "h": 1080 },
"zIndex": 0,
"config": {
"assetRef": { "assetId": "ast_01ARZ3NDEKTSV4RRFFQ69G5FAV", "variantSet": [] },
"pages": { "numbers": [1, 2, 3], "of": 3 },
"display": "sequence",
"fit": "page",
"interval": 10,
"corners": "round",
"surface": "dark"
}
}
Kürzer geht es auch: Weil jedes Feld außer content eine Vorgabe trägt, ist { "content": "Willkommen" } bereits eine gültige Text-Konfiguration. Die ausgeschriebene Fassung oben ist die, die der Editor speichert.
Eine Konfiguration prüfen, bevor Sie sie schreiben
Nehmen Sie das configSchema des gewünschten Widgets und geben Sie es einem Prüfer für Draft 2020-12. Der Ertrag ist eine feldgenaue Meldung, statt einer abgelehnten Anfrage.
import Ajv2020 from 'ajv/dist/2020.js';
const katalog = await (await fetch(`${api}/v1/widget-catalog`, { headers })).json();
const eintrag = katalog.entries.find(
(e) => e.appId === 'core' && e.definition.id === 'text',
);
const pruefe = new Ajv2020({ allErrors: true }).compile(eintrag.definition.configSchema);
const config = { content: 'Willkommen in Ihrer Apotheke', fontSizeRem: 3 };
if (!pruefe(config)) {
for (const fehler of pruefe.errors) {
console.error(`${fehler.instancePath || '(Wurzel)'}: ${fehler.message}`);
}
}
Drei Dinge nimmt Ihnen der Prüfer nicht ab:
- Ob es die Datei gibt, auf die
assetRefzeigt. Das beantwortet die Medienbibliothek. - Ob der Widget-Typ zu einer installierten App gehört. Widgets einer nicht installierten App weist die Plattform beim Veröffentlichen ab; die eingebauten Bausteine stehen immer offen.
- Ob die Konfiguration Sinn ergibt. Ein QR-Code mit leerer Adresse ist schema-gültig und zeigt trotzdem nur einen Platzhalter.
Das Formular im Editor
Dieser Abschnitt richtet sich an App-Autoren: Er beschreibt, wie der Szenen-Editor aus der Formular-Beschreibung einer App (config-ui.ts) das Einstellungs-Fenster und die Leiste am Widget baut. Für eine Konfiguration über die Schnittstelle ändert er nichts.
Die Rangfolge
Das Einstellungs-Fenster nimmt die Formular-Beschreibung der App, wenn sie eine hinterlegt (registry.registerConfigUI(widgetType, ui)). Nur ohne sie greift ein eigenes Formular der App (registerConfigForm), ohne beides ein aus dem Schema abgeleitetes. Deklarativ zuerst, damit gemeinsame Verbesserungen (Gliederung, eingeklappte Gruppen, Hinweise, Ort-Feld) jede App erreichen, ohne dass sie ihr Formular nachzieht. Eine App im neuen Standard registriert deshalb kein ganzes Formular mehr, sondern höchstens einzelne Felder.
Eigene Felder: registerConfigField
Was keine Feldart abbildet (eine Stadtsuche, eine Auswahl aus eigenen Daten), bringt die App als einzelnes Bedienelement mit:
import type { AuthoringRegistry, ConfigFieldProps } from '@szenera/widget-editor/registry';
import type { GeoPlace } from '@szenera/apps-sdk/config';
function CityPicker({ value, onChange, apiFetch, context }: ConfigFieldProps<GeoPlace | null>) {
// sucht über apiFetch die Städte der App, context.locale für die Schreibweise
}
export function registerAuthoring(registry: AuthoringRegistry): void {
registry.registerConfigUI('weather', weatherConfigUI);
registry.registerConfigField('weather', 'location.place', CityPicker as never);
}
Der Schlüssel ist der Punkt-Pfad des Felds (city) oder eines Teils davon (location.place ist die Ortsauswahl des Ort-Felds location). Das Formular rendert Beschriftung, Hilfetext, Pflicht-Stern und Fehler um das Bedienelement herum. Es bekommt:
| Prop | Inhalt |
|---|---|
value, onChange |
Der Wert an diesem Schlüssel und der Weg, ihn zu schreiben. |
field |
Die deklarierte Beschreibung des Felds. |
config |
Die ganze Config des Entwurfs, nur zum Lesen. |
error |
Die Meldung, wenn der Entwurf hier gegen das Schema verstößt. |
context |
Rahmen des Widgets in Bühnenpixeln, seine Form und Größenklasse (band, wide, square, tall, dieselbe Einordnung wie useWidgetFormat), Sprache und Zeitzone der Organisation und ihre Displays (Name, Standortname, ob der Standort Koordinaten hat). |
apiFetch |
Der Transport der App für ihre eigenen Routen, wie bei einem eigenen Formular. |
Feldarten mit eigenem Bedienelement
| Feldart | Im Fenster | An der Leiste |
|---|---|---|
location |
Wahl „Standort des Displays“ oder „Fester Ort“ (ohne Display-Bezug mit allowDisplay: false nur der feste Ort). Beim Display-Bezug ein Satz, welche Displays keinen Standort mit Anschrift haben, mit fallback: true ein Ersatzort. Die Ortsauswahl ist die der App (<feld>.place), sonst eine einfache Eingabe aus Name, Land, Breite und Länge. |
nicht kompakt, nur im Fenster |
surface |
Kacheln mit Farbmuster und Beschriftung, eine aktiv. | Auswahl mit Muster |
Gruppe mit collapsed: true |
Aufklapper, zu beim Öffnen, offen, solange darin ein Fehler steht. | – |
required: true |
Pflicht-Stern an der Beschriftung. Ob ein Wert fehlt, entscheidet das Schema. | – |
Schnellfelder: quickFields
Höchstens zwei Felder kompakter Art (color, segmented, select, slider, toggle, surface) nennt quickFields, in der Reihenfolge der Leiste. Andere Arten und mehr als zwei Felder lässt die Leiste weg, defineWidgetConfigUI() meldet das schon beim Bau. Neben den Schnellfeldern zeigt die Leiste bei einem Widget mit mehreren Vorlagen die Vorlagen-Auswahl (Widget, Konfiguration und Vorschau).
Der Passt-Bericht: Schlüssel sind Feldnamen
Blendet die Laufzeit einen eingeschalteten Inhalt aus, weil der Rahmen zu klein ist, meldet sie ihn mit useFitReport().reportHidden(parts). Die Teile heißen wie die Felder der Config, die den Inhalt einschalten, etwa ['showDaily']. Der Editor zeigt dann unter der Vorschau „In diesem Rahmen ausgeblendet: Nächste Tage.“ mit der Beschriftung des Felds aus der Formular-Beschreibung und unter dem Schalter einen Satz, warum er nichts bewirkt. Ein Teil ohne passendes Feld erscheint mit seinem Schlüssel.
Weiterführendes
- Widget, Konfiguration und Vorschau — wie eine App ihre Widget-Definition schreibt, aus der dieses Schema entsteht.
- Signieren und der Weg in den Katalog — die beiden Kataloge über alle Apps und ihre Abfragen.
- Datenzugriff, Rechte und Bedingungen — was eine App vor der Installation offenlegt.