DokumentationMethacore Apps

app.json-Referenz

14 min Lesezeit · zuletzt aktualisiert 23. September 2026

app.json beschreibt eine gehostete App vollständig: welche Bausteine sie zeigt, woher die Daten kommen, was bei einem Klick passiert. Der Builder schreibt diese Datei, im Bereich Code kannst du sie direkt bearbeiten. Beides ist gleichwertig – der Builder kann alles, was die Datei kann.

Diese Referenz entsteht aus der Runtime selbst (@methacore/runtime, Schema-Version 1). Das JSON-Schema liegt unter https://methacore.de/schemas/apps/app/v1.json; mit "$schema" bekommst du in jedem Editor Autovervollständigung.

Aufbau

{
  "$schema": "https://methacore.de/schemas/apps/app/v1.json",
  "schemaVersion": 1,
  "name": "Pinnwand",
  "auth": { "clientId": "cl_7d2e41a9", "scopes": ["mc.apps.storage:read", "mc.apps.storage:write"] },
  "initialState": { "entwurf": "" },
  "root": { "id": "root", "type": "Stack", "children": [] }
}
FeldTypPflichtBedeutung
$schemastringneinAdresse des JSON-Schemas, nur für Editoren
schemaVersion1jaVersion des Formats. Andere Werte lehnt die Runtime ab.
namestringneinName der App. Das Studio hält ihn gleich mit dem DevHub-Eintrag.
auth.clientIdstringfür DatenOAuth-Client der App. Ohne ihn kann die App keine Daten laden.
auth.scopesstring[]für DatenScopes, denen Mitglieder beim ersten Öffnen zustimmen. Siehe Berechtigungen.
initialStateobjectneinStartwerte des State
rootKnotenjaDer oberste Baustein, meist ein Stack

Knoten

Jeder Baustein ist ein Knoten:

{
  "id": "speichern",
  "type": "Button",
  "props": { "label": "Speichern", "variant": "filled" },
  "bind": [],
  "on": { "click": [{ "type": "toast", "message": "Gespeichert" }] },
  "children": []
}
FeldBedeutung
ideindeutig in der ganzen App. Taucht im HTML als data-mc-id auf – damit sprichst du den Baustein in styling.css an.
typeeiner der Bausteine unten
propsEigenschaften. Jeder Wert darf {{…}}-Ausdrücke enthalten.
bindDatenbindungen: Datenquellen, die beim Start geladen werden
onEreignisse mit einer Liste von Aktionen
childrenKind-Knoten – nur bei Containern: Stack, Grid, Card, Tabs, List

Zusätzliche Felder am Knoten sind nicht erlaubt; die Runtime meldet einen Fehler, statt sie still zu ignorieren.

Bausteine

BausteinIm BuilderGruppeKinderEreignisse
StackStapelLayoutjaload
GridRasterLayoutja
CardKarteLayoutja
TabsTabsLayoutjachange
HeadingÜberschriftInhalt
TextTextInhalt
ImageBildInhalt
ButtonButtonFormularclick
TextInputTextfeldFormularchange, submit
SelectAuswahlFormularchange
CheckboxCheckboxFormularchange
DateInputDatumFormularchange
TableTabelleDatenrowClick
ListListeDatenjaclick
ChartDiagrammDaten

Zwei Eigenschaften hat jeder Baustein:

EigenschaftBedeutung
visibleAusdruck, der den Baustein ein- oder ausblendet. Ausgeblendet ist er bei false, "false", einem leeren Text oder wenn der Ausdruck auf nichts zeigt. Fehlt die Eigenschaft, ist er immer sichtbar.
classNameeigene CSS-Klassen, durch Leerzeichen getrennt. Im Builder heißt das Feld „CSS-Klasse“.

Stack – Stapel

EigenschaftIm BuilderBedeutung
gapAbstand (px)Abstand zwischen den Elementen in Pixeln. Neu im Builder: 12.
directionRichtungOb die Elemente unter- oder nebeneinander stehen. Werte: column (Untereinander), row (Nebeneinander).
labelTab-Titel (in Tabs)Beschriftung, die neben oder über dem Element steht.
alignnur im CodeAusrichtung quer zur Richtung: start, center, end oder stretch.
justifynur im CodeVerteilung entlang der Richtung: start, center, end oder between.

Das load-Ereignis wirkt nur am Wurzelknoten. label braucht ein Stapel nur als Kind von Tabs.

Grid – Raster

EigenschaftIm BuilderBedeutung
columnsSpaltenWie viele Spalten nebeneinander stehen. Neu im Builder: 2.
minWidthMindestbreite je Spalte (px)Schmaler wird keine Spalte; darunter bricht das Raster um.
gapAbstand (px)Abstand zwischen den Elementen in Pixeln. Neu im Builder: 12.
labelTab-Titel (in Tabs)Beschriftung, die neben oder über dem Element steht.

Mit minWidth bricht das Raster von selbst um; sonst gilt columns (1–12).

Card – Karte

EigenschaftIm BuilderBedeutung
titleTitelÜberschrift der Karte. Neu im Builder: "Karte".
labelTab-Titel (in Tabs)Beschriftung, die neben oder über dem Element steht.

label braucht eine Karte nur als Kind von Tabs.

Tabs – Tabs

EigenschaftIm BuilderBedeutung
defaultTabnur im CodeIndex des Tabs, der zuerst offen ist, ab 0.

Jedes Kind ist ein Tab; sein props.label ist die Beschriftung und Pflicht. change liefert { value: <Index> }.

Heading – Überschrift

EigenschaftIm BuilderBedeutung
textTextAngezeigter Text. {{…}} setzt Werte aus dem State ein. Neu im Builder: "Überschrift".
levelEbeneGröße der Überschrift, H1 ist die größte. Werte: 1 (H1), 2 (H2), 3 (H3), 4 (H4). Neu im Builder: 2.

Text – Text

EigenschaftIm BuilderBedeutung
textTextMit {{state.schlüssel}} fügst du Werte ein. Neu im Builder: "Text".
sizeGrößeSchriftgröße des Texts. Werte: sm (Klein), md (Normal), lg (Groß).
mutedGedämpftGrauer, zurückhaltender Text für Nebeninformationen.
alignnur im CodeAusrichtung: left, center oder right.

Image – Bild

EigenschaftIm BuilderBedeutung
srcBild-URL (https)Adresse des Bilds, muss mit https beginnen.
altAlternativtextBeschreibung für Screenreader und wenn das Bild nicht lädt.
widthBreite (px)Breite in Pixeln, leer für die volle Breite.
radiusEcken (px)Abrundung der Ecken in Pixeln.

Geladen werden nur Adressen mit https: oder data:image/.

Button – Button

EigenschaftIm BuilderBedeutung
labelBeschriftungBeschriftung, die neben oder über dem Element steht. Neu im Builder: "Button".
variantVarianteAussehen des Knopfs. Werte: filled (Gefüllt), light (Hell), outline (Umrandet), subtle (Dezent).
fullWidthVolle BreiteNimmt die ganze verfügbare Breite ein.
disabledDeaktiviertSperrt das Element, es lässt sich nicht bedienen.
colornur im Codered macht einen roten Knopf, z. B. zum Löschen (Klasse .mc-button--danger).

Die Klasse ist .mc-button--primary für gefüllte, .mc-button--secondary für helle, umrandete und dezente Knöpfe.

TextInput – Textfeld

EigenschaftIm BuilderBedeutung
labelBeschriftungBeschriftung, die neben oder über dem Element steht. Neu im Builder: "Textfeld".
modelState-SchlüsselDer eingegebene Wert landet in diesem State-Schlüssel.
placeholderPlatzhalterGrauer Hinweis im leeren Feld, z. B. ein Beispiel.
typeTypArt der Eingabe, steuert Tastatur und Prüfung. Werte: text (Text), email (E-Mail), number (Zahl), search (Suche).
requiredPflichtfeldDas Feld muss ausgefüllt werden.
valuenur im CodeStartwert, wenn kein model gesetzt ist.
disablednur im CodeSperrt das Feld.

change kommt bei jeder Eingabe mit { value }, submit bei Enter. Mit type: "number" landet eine Zahl (oder null) im State.

Select – Auswahl

EigenschaftIm BuilderBedeutung
labelBeschriftungBeschriftung, die neben oder über dem Element steht. Neu im Builder: "Auswahl".
modelState-SchlüsselDer eingegebene Wert landet in diesem State-Schlüssel.
optionsOptionenListe wie ["A", "B"] oder [{"value": "a", "label": "A"}], oder {{state.liste}}. Neu im Builder: ["Option 1","Option 2"].
optionValueFeld für den WertFeld jeder Zeile, das bei der Auswahl gespeichert wird.
optionLabelFeld für den TextFeld jeder Zeile, das in der Auswahl zu lesen ist.
placeholderPlatzhalterGrauer Hinweis im leeren Feld, z. B. ein Beispiel.
valuenur im CodeStartwert, wenn kein model gesetzt ist.
disablednur im CodeSperrt die Auswahl.

options ist eine Liste von Texten oder Objekten. Mit optionValue und optionLabel passt eine Datenquelle direkt: "options": "{{state.gruppen}}", "optionValue": "id", "optionLabel": "name".

Checkbox – Checkbox

EigenschaftIm BuilderBedeutung
labelBeschriftungBeschriftung, die neben oder über dem Element steht. Neu im Builder: "Checkbox".
modelState-SchlüsselDer eingegebene Wert landet in diesem State-Schlüssel.
valuenur im CodeStartwert, wenn kein model gesetzt ist.
disablednur im CodeSperrt die Checkbox.

Im State landet true oder false; change liefert { value }.

DateInput – Datum

EigenschaftIm BuilderBedeutung
labelBeschriftungBeschriftung, die neben oder über dem Element steht. Neu im Builder: "Datum".
modelState-SchlüsselDer eingegebene Wert landet in diesem State-Schlüssel.
withTimeMit UhrzeitZusätzlich zur Uhrzeit fragen.
placeholdernur im CodeGrauer Hinweis im leeren Feld.
requirednur im CodeDas Feld muss ausgefüllt werden.
disablednur im CodeSperrt das Feld.
valuenur im CodeStartwert, wenn kein model gesetzt ist.

Im State landet der Wert des Browsers: 2026-10-02 bzw. mit Uhrzeit 2026-10-02T17:00.

Table – Tabelle

EigenschaftIm BuilderBedeutung
dataDatenquelle (Ausdruck)Liste aus dem State, die die Tabelle zeigt.
columnsSpalten (JSON)z. B. [{"key": "name", "label": "Name"}]. Leer = alle Felder.
emptyText ohne EinträgeSteht da, solange es keine Einträge gibt. Neu im Builder: "Keine Einträge".

columns darf auch eine Liste von Feldnamen sein: ["title", "start"]. Bei rowClick steht die Zeile als item (und event) in den Aktionen.

List – Liste

EigenschaftIm BuilderBedeutung
dataDatenquelle (Ausdruck)Liste aus dem State, deren Einträge die Liste zeigt.
itemTextText je Eintrag (Ausdruck)Gilt nur ohne Kind-Elemente. {{item.feld}} greift auf den Eintrag zu.
emptyText ohne EinträgeSteht da, solange es keine Einträge gibt.

Ohne Kinder zeigt jeder Eintrag itemText. Mit Kindern werden sie je Eintrag gezeichnet; darin gibt es {{item}} und {{index}}. itemText wird je Eintrag ausgewertet. click liefert den Eintrag als item.

Chart – Diagramm

EigenschaftIm BuilderBedeutung
dataDatenquelle (Ausdruck)Liste aus dem State, die das Diagramm zeigt.
xFeld für BeschriftungFeldname jeder Zeile für die Beschriftung.
yFeld für WertFeldname jeder Zeile für den Zahlenwert.
kindArtDarstellung des Diagramms. Werte: bar (Balken), line (Linie). Neu im Builder: "bar".
heightHöhe (px)Höhe in Pixeln.
colornur im CodeFarbe der Balken bzw. der Linie. Standard: var(--mc-color-primary).
titlenur im CodeBeschreibung des Diagramms für Screenreader.

Ein schlichtes SVG-Diagramm. Beschriftungen kommen aus dem Feld x (Standard label), Werte aus y (Standard value).

Ausdrücke {{…}}

Ausdrücke setzen Werte aus dem State oder dem Kontext ein. Sie sind reine Pfade: kein Rechnen, keine Vergleiche, keine Funktionsaufrufe. Alles, was mehr braucht, gehört in einen Handler.

{{state.profil.firstName}}        Wert aus dem State
{{state.termine[0].title}}        Listeneintrag über den Index (auch state.termine.0.title)
{{item.name}}                     Eintrag einer Liste oder angeklickte Tabellenzeile
{{event.value}}                   Wert eines change- oder submit-Ereignisses
{{context.organisation.name}}     Kontext des Hosts

Was verfügbar ist:

NameInhalt
stateder ganze State der App
state.$loadingje State-Schlüssel einer Bindung true, solange sie lädt, z. B. {{state.$loading.termine}}
contextvom Host: organisation (id, name), user (id, firstName), theme (light/dark), locale (de/en), target (dashboard/mobile)
item, indexin Kindern einer List und in Aktionen von rowClick bzw. click einer Liste
eventin Aktionen: die Nutzlast des Ereignisses

Zwei Regeln bestimmen das Ergebnis:

  • Steht der Ausdruck allein, bekommst du den Wert selbst – eine Liste bleibt eine Liste, eine Zahl eine Zahl. So übergibst du {{state.termine}} an eine Tabelle.
  • Steht er in einem Text, wird er zu Text: "Hallo {{state.profil.firstName}}!". Objekte erscheinen dann als JSON, fehlende Werte als leerer Text.

Pfade auf __proto__, prototype oder constructor liefern immer nichts.

Datenbindungen

Eine Bindung lädt eine Datenquelle beim Start der App und legt das Ergebnis im State ab:

"bind": [
  { "source": "me.events", "to": "termine", "pick": "items" },
  { "source": "storage.list", "params": { "collection": "notizen", "scope": "org" }, "to": "notizen", "pick": "items" }
]
FeldPflichtBedeutung
sourcejaName der Datenquelle, z. B. me.profile
paramsneinParameter der Quelle; Werte dürfen {{…}} enthalten
tojaState-Schlüssel, der das Ergebnis bekommt. Punkte trennen Ebenen: daten.termine. Muss mit einem Buchstaben, _ oder $ beginnen.
pickneinPfad im Ergebnis, der gespeichert wird, z. B. items bei Listen oder card beim Ausweis

Alle Bindungen aller Knoten starten gleichzeitig, sobald die App geladen ist. Die Parameter werden einmal gegen den Start-State ausgewertet. Danach laufen die load-Aktionen des Wurzelknotens. Schlägt eine Bindung fehl, zeigt die App einen roten Hinweis; die übrigen laufen weiter.

Um Daten später neu zu laden – etwa nach dem Speichern –, nimmst du die Aktion call mit demselben to.

Ereignisse

EreignisIm BuilderBausteineevent
loadBeim LadenStack
clickBeim KlickButton, Listbei List der angeklickte Eintrag, sonst –
changeBei ÄnderungTextInput, Select, Checkbox, DateInput, Tabs{ value }, bei Tabs der Index
submitBeim Absenden (Enter)TextInput{ value }
rowClickBeim Klick auf eine ZeileTabledie angeklickte Zeile

load wirkt nur am Wurzelknoten. Ein Ereignis führt seine Aktionen nacheinander aus; wirft eine davon einen Fehler, bricht die Kette ab und die App zeigt „Aktion fehlgeschlagen: …“.

Aktionen

AktionIm BuilderZweck
callDaten abrufeneine Datenquelle aufrufen, das Ergebnis optional im State ablegen
exportExportiereneine Liste als CSV, XLSX oder PDF herunterladen bzw. teilen
navigateNavigierenden Host zu einer seiner Seiten schicken
setStateState setzeneinen State-Schlüssel setzen
toastHinweis anzeigeneinen Hinweis im Host zeigen
scriptSkript-Handler (main.js)einen Handler aus main.js aufrufen

call – Daten abrufen

{ "type": "call", "source": "storage.put",
  "params": { "collection": "notizen", "key": "{{state.neu.key}}", "value": "{{state.neu.text}}" } }
FeldPflichtBedeutung
sourcejaDatenquelle
paramsneinParameter, mit Ausdrücken
toneinState-Schlüssel für das Ergebnis; ohne to wird das Ergebnis verworfen
pickneinPfad im Ergebnis

export – Exportieren

{ "type": "export", "format": "csv", "data": "{{state.termine}}", "filename": "termine-{{context.organisation.name}}",
  "columns": [{ "key": "title", "label": "Termin" }, { "key": "start", "label": "Beginn" }], "excel": true }
FeldPflichtBedeutung
formatjacsv, xlsx oder pdf
datajaAusdruck, der eine Liste ergibt
filenamejaDateiname, die Endung ergänzt die Runtime
columnsneinSpalten mit key und optional label; ohne Angabe alle Felder der ersten Zeile
excelneinnur CSV: Semikolon und BOM für deutsches Excel (Standard)
titleneinnur PDF: Überschrift über der Tabelle

Die Datei geht über die Bridge an den Host, der sie herunterlädt (Dashboard) oder teilt (Handy).

{ "type": "navigate", "path": "/apps" }

Bittet den Host, zu einer seiner eigenen Seiten zu wechseln – ein Pfad des Dashboards bzw. der Mitglieder-App, keine fremde Adresse. Ziele, die es in der Mitglieder-App nicht gibt, lehnt sie mit einem Fehler ab. Außerhalb eines Hosts setzt die Runtime nur den Hash der Seite.

setState – State setzen

{ "type": "setState", "key": "auswahl", "value": "{{item}}" }

value darf ein Ausdruck oder ein fester Wert sein, auch ein Objekt. Ohne value wird der Schlüssel geleert.

toast – Hinweis anzeigen

{ "type": "toast", "message": "{{state.notizen.length}} Notizen geladen", "color": "green" }

color: green, blue, orange oder red. Der Host zeigt den Hinweis in seinem eigenen Stil.

script – Handler aus main.js

{ "type": "script", "handler": "speichern", "args": { "collection": "notizen" } }

Ruft die gleichnamige exportierte Funktion aus main.js auf. args wird vorher ausgewertet. Mehr unter main.js & Handler.

Beispiel: Pinnwand

Eine App, die Notizen des Vereins im App-Speicher ablegt und anzeigt – ganz ohne main.js:

{
  "schemaVersion": 1,
  "name": "Pinnwand",
  "auth": { "clientId": "cl_7d2e41a9", "scopes": ["mc.apps.storage:read", "mc.apps.storage:write"] },
  "initialState": { "neu": "" },
  "root": {
    "id": "root",
    "type": "Stack",
    "bind": [{ "source": "storage.list", "params": { "collection": "pinnwand" }, "to": "notizen", "pick": "items" }],
    "children": [
      { "id": "titel", "type": "Heading", "props": { "text": "Pinnwand {{context.organisation.name}}", "level": 2 } },
      {
        "id": "zeile",
        "type": "Stack",
        "props": { "direction": "row", "gap": 8 },
        "children": [
          { "id": "eingabe", "type": "TextInput", "props": { "label": "Neue Notiz", "model": "neu" } },
          {
            "id": "anheften",
            "type": "Button",
            "props": { "label": "Anheften" },
            "on": {
              "click": [
                { "type": "call", "source": "storage.put",
                  "params": { "collection": "pinnwand", "key": "{{state.neu}}", "value": "{{state.neu}}" } },
                { "type": "call", "source": "storage.list", "params": { "collection": "pinnwand" }, "to": "notizen", "pick": "items" },
                { "type": "setState", "key": "neu", "value": "" }
              ]
            }
          }
        ]
      },
      {
        "id": "liste",
        "type": "List",
        "props": { "data": "{{state.notizen}}", "itemText": "{{item.value}}", "empty": "Noch nichts angeheftet" }
      }
    ]
  }
}

Der Schlüssel ist hier der Text selbst – das hält das Beispiel kurz, erlaubt aber nur Zeichen aus A–Z a–z 0–9 _ -. Eine echte Pinnwand erzeugt Schlüssel in einem Handler.

Validierung

Die Runtime prüft app.json beim Laden, das Studio schon beim Tippen. Typische Meldungen:

MeldungUrsache
schemaVersion must be 1falsche oder fehlende Version
root (x): duplicate id "x"zwei Knoten mit derselben id
unknown component type "Foo"Tippfehler im type
root.children[0]: a tab needs props.labelein Tab ohne Beschriftung
bind[0]: invalid state key "1termine"to beginnt mit einer Ziffer oder enthält Leerzeichen
on.click[0]: unknown action type "open"Aktion gibt es nicht
invalid handler name "mein-handler"Handler-Namen folgen den Regeln für JavaScript-Bezeichner

Ältere Dokumente repariert das Studio beim Öffnen selbst: leere State-Schlüssel bekommen einen lesbaren Namen, Tabs ohne Titel heißen „Tab 1“, „Tab 2“ usw.