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 für dich liest
Diese schreibst du normalerweise nie - createCmssyPage, CmssyServerLayout und die Formularauflösung hinter context.forms senden sie intern.
public.page.get- eine Seite per Slug:{ id, blocks, publishedBlocks }plus die SEO-FelderseoTitle,seoDescription,seoKeywords,displayName.public.page.getById-{ id, publishedBlocks }.public.page.list-[{ id, slug, updatedAt, publishedAt }].public.page.layouts-[{ position, blocks, settings }].public.siteConfig- Site-Name, Standard- und aktivierte Sprachen, Features, Branding.public.form.get- Formularfelder und -einstellungen, für Blöcke alscontext.forms.public.form.submit-{ success, message, submissionId, redirectUrl }.
Die Fetch-Helper, die sie kapseln - fetchPage, fetchPages, fetchLayouts, fetchPageMeta, fetchSiteConfig, resolveSiteLocales, resolveForms - liegen unter @cmssy/core/internal und sind keine öffentliche API. Öffentlich sind createCmssyClient und graphqlRequest: Alles, was createCmssyPage nicht ohnehin rendert, fragst du selbst ab.
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
) {
public {
page {
byType(
workspaceId: $workspaceId
parentSlug: $parentSlug
search: $search
limit: $limit
offset: $offset
) {
items {
id
slug
fullSlug
publishedAt
displayName
seoTitle
seoDescription
customFields
pageType
}
total
hasMore
}
}
}
}byType nimmt außerdem pageType, sortBy und customFieldFilters - und mit gültigem previewSecret auch includeDrafts.
Ein Formular absenden
mutation SubmitForm($formId: ID!, $input: SubmitFormInput!) {
public {
form {
submit(formId: $formId, input: $input) {
success
message
submissionId
redirectUrl
}
}
}
}Übergib { formId, input: { data } }. Das SDK führt denselben String als SUBMIT_FORM_MUTATION, aber unter @cmssy/core/internal - halte lieber deine eigene Kopie, statt ihn zu importieren.
Member-Auth mountest du selbst
Die siteMember-Mutationen - login, register, refresh, logout, forgotPassword, resetPassword, verifyEmail - tragen den Member-Auth-Flow.
Das schlanke SDK liefert weder eine Auth-Route noch einen Session-Reader. login und refresh geben dir die rohen accessToken und refreshToken, und das Backend setzt kein Cookie - die Session gehört deiner App. Ein httpOnly-Cookie, das dein eigener Route Handler schreibt, ist der sichere Standard. Den ganzen Flow von der Registrierung bis zur E-Mail-Verifizierung zeigt Member-Authentifizierung.
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.