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}/graphql

apiBase 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. Rufe client.queryScoped(...): Deklariert deine Query $workspaceId und du übergibst ihn nicht, löst das SDK ihn aus workspaceSlug auf und ergänzt Variable und x-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-Felder seoTitle, 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 als context.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

  • data und filter nutzen den JSON-Skalar - übergib einfache Objekte, kein stringifiziertes JSON.
  • customFields einer Seite ist eine JSON-Map der Custom Fields ihres Seitentyps.
  • Reads liefern nur veröffentlichte Inhalte, sofern kein gültiges previewSecret mitkommt. Im Edit-Modus erledigt das SDK das mit deinem draftSecret.

Nächste Schritte