Zum Inhalt springen

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

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 required steht nur, was die Plattform nicht selbst einsetzen kann — meistens der Inhalt. Alles andere trägt ein default, 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 examples ist 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 assetRef zeigt. 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