Browse all documentation

Add an optional manifest-derived GraphQL endpoint with typed resources, shared authorization, and bounded execution.

GraphQL is an optional compiled transport. REST-only applications do not import it and therefore do not link the parser, executable schema, handler, or GraphQL runtime into their binary.

The plugin derives one immutable executable schema from the canonical Ridu manifest at startup. Resolvers enter the same local operation engine as REST and the SDK; GraphQL selection does not bypass access, validation, hooks, transactions, localization, population, or redaction.

Use it in a new project

Create an ordinary starter or blank project, then add the graphqlplugin.New() registration below. GraphQL is Go-only and has no admin package to install: applications that do not import it do not link its parser or execution runtime into the binary.

Add it to an existing project

Import github.com/riducms/ridu/plugins/graphql from the published Ridu module already used by the project, add it to Config.Plugins, and run go mod tidy. Do not add an npm package or generated admin import; this plugin owns a transport, not an authoring control.

Enable the endpoint

content/config.go
package content

import (
  "github.com/riducms/ridu"
  graphqlplugin "github.com/riducms/ridu/plugins/graphql"
)

func Config() ridu.Config {
  return ridu.Config{
    Name: "Acme Editorial",
    Plugins: []ridu.Plugin{
      graphqlplugin.New(),
    },
    Collections: []ridu.Collection{Users, Posts},
  }
}

The default route is POST /api/graphql. An invalid GraphQL name, generated type/root collision, unknown resource override, or malformed extension stops startup instead of hiding part of the schema. Ridu does not serve a browser playground.

Start the complete development loop:

terminal
go mod tidy
npm run dev

ridu dev regenerates the committed SDL and other contracts before starting the API and admin. Enabling only the transport does not add stored fields or adapter tables, so it does not need a database migration. If the same change also modifies resources, prepare the selected adapter’s reviewed migration before deployment. Run npm run ridu -- check before committing so generated SDL drift is caught.

With the server running, send an authenticated bounded query to POST /api/graphql. Also confirm that an unauthorized query receives the same denial and field redaction as REST.

Query collections

Root names come from resolved singular/plural labels. A posts collection normally produces Post, Posts, and countPosts queries:

graphql
query RecentPosts {
  Posts(
    where: { featured: { equals: true }, score: { greater_than: 5 } }
    sort: ["-score"]
    page: 1
    limit: 20
  ) {
    docs {
      id
      title
      category {
        id
        name
      }
    }
    totalDocs
    totalPages
    hasNextPage
  }
}

Typed filter inputs compile into Ridu’s finite query vocabulary. Nested group, array, and block filter paths use flattened double underscores such as seo__description and links__label. Selected relationship/upload fields become bounded operation-engine population and reapply target access and redaction.

Groups and arrays have typed nested output/input objects. Select/radio choices use GraphQL enums. Blocks return typed output unions selected with inline fragments; mutation input uses JSON because GraphQL has no input union. Polymorphic references use { relationTo, value }.

Create and mutate

Collections add generated create, update, delete, and duplicate fields when mutations are enabled:

graphql
mutation CreatePost($category: ID!) {
  createPost(data: { title: "Hello", category: $category }) {
    id
    title
    category {
      id
      name
    }
  }
}

Optimistic mutations accept expectedRevision when the resource is versioned. Duplicate runs the ordinary create lifecycle. Upload-enabled collections deliberately omit GraphQL create: JSON cannot carry the byte source or server-owned metadata. Use multipart/remote upload APIs for new files; upload document reads and metadata remain available in GraphQL.

Globals, drafts, versions, and trash

Globals expose read/update and matching version/draft operations. Versioned collections add single/list version queries and restore; draft-enabled resources add publish/unpublish. Trash adds trash: true reads, restore-deleted, and permanent-delete mutations. draft: true includes or writes a draft; draft: false selects published behavior.

These operations use the same revision checks, access rules, hooks, retention, reference cleanup, and scheduled-state semantics as other transports. Scheduling, document locks, previews, bulk operations, and raw file transfer remain REST/SDK-focused surfaces rather than inferred GraphQL fields.

Localized content

Configured locales become a deterministic RiduLocale enum:

graphql
query LocalizedPost($id: ID!) {
  english: Post(id: $id, locale: EN) {
    title
  }
  french: Post(id: $id, locale: FR, fallbackLocale: [EN]) {
    title
  }
  exactArabic: Post(id: $id, locale: AR, disableFallback: true) {
    title
  }
}

The selected locale reaches filters, relationships, access, hooks, and versions. GraphQL does not overload scalar fields with the local/REST all locale-keyed shape; request one locale per field alias when a query needs several translations.

Authentication and preferences

Auth collections generate login, current-user/session, refresh, logout, initialized-state, unlock, recovery, and verification fields according to configuration. The result contains an opaque token, expiry, exact auth collection, and redacted user.

Browser applications should prefer the REST/SDK HTTP-only cookie. A non-browser GraphQL client can authenticate with:

text
Authorization: Session <opaque-session-token>
Authorization: Bearer ridu_<id>_<api-key-secret>

Session and API-key identity includes its auth collection, so colliding document IDs across auth collections remain distinct. Admin-identity GraphQL clients also get preference read/set/delete/ reset fields with the same exact ownership contract.

Configure resources

Rename or suppress a generated GraphQL surface without changing the manifest slug, REST path, or SDK type:

go
graphqlplugin.New(graphqlplugin.Options{
  Resources: map[string]graphqlplugin.ResourceOptions{
    "posts": {
      SingularName:     "Article",
      PluralName:       "Articles",
      DisableMutations: true,
    },
  },
})

Disabling a GraphQL query/mutation removes only that transport field. It is not an access rule for REST, the local API, SDK, admin, or plugin endpoints.

Add trusted root fields

Compiled extension queries/mutations receive the authenticated actor, exact auth collection, local API, and public application facade—never a store adapter:

go
graphqlplugin.New(graphqlplugin.Options{
  Queries: []graphqlplugin.ExtensionField{
    {
      Name: "postTotal",
      Type: graphql.NewNonNull(graphql.Int),
      Cost: 5,
      Resolve: func(input graphqlplugin.ExtensionContext) (any, error) {
        page, err := input.Local.List(input.Context, "posts", ridu.ListOptions{
          Page: 1, Limit: 1,
          Actor: input.Actor, ActorCollection: input.ActorCollection,
        })
        return page.Total, err
      },
    },
  },
})

Using the supplied API keeps application authorization and lifecycle behavior intact. Extension code is trusted compiled code; Cost protects query budgeting, not sandboxing.

Generate deterministic SDL

Configure a project-owned destination after enabling the plugin:

ridu.toml
generated.graphql.schema = "./generated/ridu.graphql"

The development loop now writes the exact compiled schema without enabling network introspection:

bash
npm run dev

Use npm run ridu -- generate --check in CI to verify that the committed SDL matches executable config. Use npm run ridu -- generate only when you need a one-shot write without starting the development server.

Resource renames, disabled surfaces, and custom root fields come from the same executable plugin instance used at runtime. They are never duplicated into the serializable manifest. Ridu validates the project-relative path through the generic generated.<plugin>.<artifact> mapping and installs the SDL atomically beside the other contracts. graphqlplugin.GenerateSDL remains available when application-owned Go tooling needs the bytes directly.

Before removing the GraphQL plugin, remove its generated mapping and delete the now-unowned SDL file. Ridu never deletes paths that disappear from ridu.toml.

Resource safeguards

Secure zero-value defaults are:

Limit Default
HTTP body 1 MiB
Encoded variables 256 KiB
Executable depth 12
Aliases 100
Total complexity 1,000
Per-list limit 100
Introspection disabled

Fragments are expanded for list/cost analysis, nested join limits are charged, and cycles fail closed. Hard syntax/token/fragment ceilings remain even if application options are raised. Keep introspection disabled on untrusted production endpoints unless your operational policy needs it.

go
graphqlplugin.New(graphqlplugin.Options{
  AllowIntrospection: true,
  MaxComplexity:      2_000,
  MaxDepth:           16,
})

GraphQL validation failures use the normal response shape. Operation errors expose stable Ridu code, status, and validation issues extensions; unexpected internal failures do not expose a message or stack.

See the graphql plugin reference, REST API, and TypeScript SDK. Use Capability status to track the remaining conformance boundary.