Fumadocs

Headless

The state and logic of GraphQL pages, without UI.

Overview

Everything outside @fumadocs/graphql/ui is headless.

PathModule
@fumadocs/graphqlthe page: the schema built from SDL, cross-links and your components
@fumadocs/graphql/operationan operation: its field, title, directives and examples
@fumadocs/graphql/type-docsa named type: its kind, directives, relations and usages
@fumadocs/graphql/playgroundthe state of playground, sending operations and subscriptions

Page

createGraphQLRenderer() takes your components and returns <GraphQLPage />, which accepts the props of generated pages.

components/api-page.tsx
'use client';
import { type CodeBlockProps, createGraphQLRenderer } from '@fumadocs/graphql';
import { Operation } from '@/components/my-operation';
import { TypeDocs } from '@/components/my-type';

export const GraphQLPage = createGraphQLRenderer({
  components: {
    Operation,
    TypeDocs,
    // `root` is a field-like root, or a schema coordinate like `Query.orders()`
    SchemaUI: ({ client, root }) => (
      <pre>{`${client.name}: ${typeof root === 'string' ? root : root.type}`}</pre>
    ),
    CodeBlock({ lang, code }: CodeBlockProps) {
      return (
        <pre>
          <code className={`language-${lang}`}>{code}</code>
        </pre>
      );
    },
  },
});
ComponentRenders
Operationeach operation of the page, see Operation
TypeDocseach named type of the page, see Type
SchemaUIa type, argument or field in detail
Layoutoptional, wraps the rendered items

Pass typeLinks and operationLinks to override the cross-links of pre-generated pages.

Hooks

Components under the page read its state.

HookReturns
useGraphQL()the built schema, its sdl, the page links and examples
useComponents()the components passed to the page
useTypeLink(name)the page URL of a named type, undefined when unlinked
useOperationLink(kind, name)the page URL of an operation
useRenderContext()the render options of the page, like playground and codeUsages

To render operations yourself, mount <GraphQLProvider sdl={sdl} components={...} /> in place of createGraphQLRenderer().

Operation

<OperationProvider /> derives the details of an operation.

components/my-operation.tsx
'use client';
import { useComponents } from '@fumadocs/graphql';
import {
  OperationProvider,
  type PageOperationProps,
  useOperation,
} from '@fumadocs/graphql/operation';

export function Operation({ kind, name }: PageOperationProps) {
  return (
    <OperationProvider kind={kind} name={name}>
      <Content />
    </OperationProvider>
  );
}

function Content() {
  const { title, field, examples, selected, select } = useOperation();
  const { CodeBlock, SchemaUI } = useComponents();
  const example = examples.at(selected);

  return (
    <>
      <h2>{title}</h2>
      {field.args.map((arg) => (
        <SchemaUI
          key={arg.name}
          client={{ name: arg.name, rootId: arg.name }}
          root={{ type: arg.type }}
        />
      ))}
      {examples.map((item, i) => (
        <button key={i} onClick={() => select(i)}>
          {item.name ?? 'Example'}
        </button>
      ))}
      {example && <CodeBlock lang="graphql" code={example.query} />}
    </>
  );
}

examples are the ones from the examples option, or a generated one. selected is shared by everything under the provider, like the example card and the playground.

Prop

Type

Prop

Type

Type

<TypeProvider /> derives the details of a named type.

components/my-type.tsx
'use client';
import { useComponents } from '@fumadocs/graphql';
import { type PageTypeProps, TypeProvider, useNamedType } from '@fumadocs/graphql/type-docs';

export function TypeDocs({ name }: PageTypeProps) {
  return (
    <TypeProvider name={name}>
      <Content />
    </TypeProvider>
  );
}

function Content() {
  const { name, kind, type, relations } = useNamedType();
  const { SchemaUI } = useComponents();

  return (
    <>
      <h2>
        {name} ({kind})
      </h2>
      {relations.usages.returnedBy.map((op) => (
        <span key={`${op.kind}:${op.name}`}>{op.name}</span>
      ))}
      <SchemaUI client={{ name, rootId: name, as: 'body' }} root={{ type }} />
    </>
  );
}

Prop

Type

relations connects the type to the rest of schema:

RelationIs
implementsthe interfaces it implements
unionsthe unions including it, for objects
implementedBythe objects implementing it, for interfaces
possibleTypesthe members of unions
usageswhere it appears across the schema

Prop

Type

Playground

usePlayground() holds the playground of the operation under <OperationProvider />, configured by the page's playground option.

components/my-playground.tsx
'use client';
import { StfProvider, useFieldValue } from '@fumari/stf';
import { useOperation } from '@fumadocs/graphql/operation';
import { usePlayground } from '@fumadocs/graphql/playground';

export function MyPlayground() {
  const { field } = useOperation();
  const { stf, open, url, changeUrl, query, setQuery, send, isSending, response } = usePlayground();

  return (
    // opening restores the endpoint and headers of previous visits
    <details onToggle={(e) => e.currentTarget.open && open()}>
      <summary>Try it</summary>
      <input value={url} onChange={(e) => changeUrl(e.target.value)} />
      <textarea value={query} onChange={(e) => setQuery(e.target.value)} />
      <StfProvider value={stf}>
        {field.args.map((arg) => (
          <Variable key={arg.name} name={arg.name} />
        ))}
      </StfProvider>
      <button disabled={isSending} onClick={send}>
        Send
      </button>
      {response?.result.type === 'response' && <pre>{response.result.body}</pre>}
    </details>
  );
}

function Variable({ name }: { name: string }) {
  const [value, setValue] = useFieldValue(['variables', name]);

  return (
    <input
      aria-label={name}
      value={String(value ?? '')}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}

Subscriptions use subscribe(), stop(), status and log in place of send() and response.

Prop

Type

ExportDoes
useRequestValues()the variables and headers the form under <StfProvider /> sends, following its edits
executeGraphQL(request)sends a query or mutation over HTTP POST, the default fetcher
subscribeGraphQL(request, handlers)starts a subscription, returns a function to stop it
inputTypeToJsonSchema(type)the JSON Schema of an input type, for forms of variables

subscribeGraphQL() picks the protocol by the URL scheme, like subscriptionUrl.

import { subscribeGraphQL } from '@fumadocs/graphql/playground';

const stop = subscribeGraphQL(
  {
    url: 'wss://api.example.com/graphql',
    query: 'subscription { orderUpdated(id: "1") { status } }',
    headers: {},
  },
  {
    onNext: (result) => console.log(result),
    onError: (error) => console.error(error.type),
    onComplete: () => console.log('completed'),
  },
);

// no handler is called after it
stop();

Prop

Type

How is this guide?

Last updated on