GraphQL-Delivery-API
Welche Queries es gibt, wie Workspace-Scoping funktioniert und welche Reads das SDK kapselt statt dich schreiben zu lassen.
cmssy liefert veröffentlichte Inhalte über einen einzigen GraphQL-Endpunkt. Das SDK kapselt die üblichen Reads bereits - Seiten, Layouts, Site-Config, Formulare - die meisten Apps schreiben also nie eine Query. Für alles andere (eigene Modelle, Records, Kindseiten auflisten) schickst du deine eigene über den Delivery-Client.
Endpunkt und Scoping
Öffentliche Reads gehen an den org-basierten Pfad:
{apiBase}/public/{orgSlug}/{workspaceSlug}/graphqlapiBase ist dein apiUrl ohne das abschließende /graphql - standardmäßig https://api.cmssy.io, Anfragen landen also auf https://api.cmssy.io/public/{org}/{ws}/graphql. Überschreibe apiUrl nur beim Self-Hosting. org und workspaceSlug stammen aus deiner Config, den Pfad baut das SDK.
Weil die Organisation im Pfad steht, muss ein Workspace-Slug nur innerhalb seiner Organisation eindeutig sein.
Zwei Arten, eine Query zu scopen
Jede Operation ist workspace-gebunden, aber nicht alle auf dieselbe Weise. An diesem Detail stolpern die meisten:
workspaceSlug(String!) - für Seiten-, Layout-, Config- und Formular-Reads. Die Fetch-Helper des SDK übergeben ihn automatisch aus deiner Config.workspaceId(String!) - für Modell-, Record- und Page-by-Type-Reads. Rufeclient.queryScoped(...): Deklariert deine Query$workspaceIdund du übergibst ihn nicht, löst das SDK ihn ausworkspaceSlugauf und ergänzt Variable undx-workspace-id-Header.
// $workspaceId wird für dich gesetzt
await client.queryScoped(MY_QUERY, { modelSlug: "products", limit: 20 });Was das SDK bereits kapselt
Diese schreibst du normalerweise nie - der genannte Helper ruft sie für dich auf.
publicPage→fetchPage- liefert{ id, blocks, publishedBlocks }.publicPageById→fetchPageById- liefert{ id, publishedBlocks }.publicPages→fetchPages- liefert[{ id, slug, updatedAt, publishedAt }].publicPage(SEO-Felder) →fetchPageMeta- liefert{ id, seoTitle, seoDescription, seoKeywords, displayName }.publicPageLayouts→fetchLayouts- liefert[{ position, blocks }].publicSiteConfig→fetchSiteConfig/resolveSiteLocales- Site-Name, Locales, Features, Branding.public.form.get→resolveForms(undcontext.forms) - Formularfelder und -einstellungen.public.form.submit→SUBMIT_FORM_MUTATION- liefert{ success, message, submissionId, ... }.
Diese schreibst du selbst
Dafür gibt es keinen SDK-Helper. Schicke sie über client.queryScoped(...).
Records eigener Modelle
query PublicModelRecords(
$workspaceId: String!
$modelSlug: String!
$filter: JSON
$sort: String
$limit: Int
$offset: Int
$populate: [String!]
) {
public {
model {
records(
workspaceId: $workspaceId
modelSlug: $modelSlug
filter: $filter
sort: $sort
limit: $limit
offset: $offset
populate: $populate
) {
items { id modelId data status createdAt updatedAt }
total
hasMore
}
}
}
}Schreib die Query selbst und halte sie in deinem Repo. Das SDK führt zwar entsprechende Strings, aber unter @cmssy/core/internal - einem internen Subpfad, der sich zwischen Releases ohne Breaking-Change-Hinweis ändern darf. Deine eigene Query ist vier Zeilen lang und überrascht dich nie.
Kindseiten auflisten
Das treibt einen Blog-Index oder einen Docs-Baum an:
query PublicPagesByType(
$workspaceId: String!
$parentSlug: String
$search: String
$limit: Int
$offset: Int
) {
publicPagesByType(
workspaceId: $workspaceId
parentSlug: $parentSlug
search: $search
limit: $limit
offset: $offset
) {
items {
id
slug
fullSlug
publishedAt
displayName
seoTitle
seoDescription
customFields
pageType
}
total
hasMore
}
}Ein Formular absenden
mutation SubmitForm($formId: ID!, $input: SubmitFormInput!) {
public {
form {
submit(formId: $formId, input: $input) {
success
message
submissionId
redirectUrl
}
}
}
}Exportiert als SUBMIT_FORM_MUTATION. Übergib { formId, input: { data } }.
Member-Auth: nicht direkt aufrufen
Die siteMember-Mutationen - login, register, refresh, logout, forgotPassword, resetPassword, verifyEmail - tragen den Member-Auth-Flow.
Rufe sie nicht aus deinem eigenen Code auf. Mounte stattdessen createCmssyAuthRoute: Sie werden serverseitig behandelt und das Session-Cookie versiegelt. Direkte Aufrufe heißen, das Token-Sealing selbst zu übernehmen - und ein Fehler dabei ist ein Sicherheitsbug, keine kaputte Seite.
Wissenswertes
dataundfilternutzen denJSON-Skalar - übergib einfache Objekte, kein stringifiziertes JSON.customFieldseiner Seite ist eineJSON-Map der Custom Fields ihres Seitentyps.- Reads liefern nur veröffentlichte Inhalte, sofern kein gültiges
previewSecretmitkommt. Im Edit-Modus erledigt das SDK das mit deinemdraftSecret.
Nächste Schritte
- MCP-Server - der Schreibpfad.
- Server-Loader - wo eigene Queries meist leben.
- Member-Authentifizierung - der Flow hinter den
siteMember-Mutationen.