Headless
The state and logic of GraphQL pages, without UI.
Overview
Everything outside @fumadocs/graphql/ui is headless.
| Path | Module |
|---|---|
@fumadocs/graphql | the page: the schema built from SDL, cross-links and your components |
@fumadocs/graphql/operation | an operation: its field, title, directives and examples |
@fumadocs/graphql/type-docs | a named type: its kind, directives, relations and usages |
@fumadocs/graphql/playground | the state of playground, sending operations and subscriptions |
Page
createGraphQLRenderer() takes your components and returns <GraphQLPage />, which accepts the props of generated pages.
'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>
);
},
},
});| Component | Renders |
|---|---|
Operation | each operation of the page, see Operation |
TypeDocs | each named type of the page, see Type |
SchemaUI | a type, argument or field in detail |
Layout | optional, 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.
| Hook | Returns |
|---|---|
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.
'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.
'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:
| Relation | Is |
|---|---|
implements | the interfaces it implements |
unions | the unions including it, for objects |
implementedBy | the objects implementing it, for interfaces |
possibleTypes | the members of unions |
usages | where it appears across the schema |
Prop
Type
Playground
usePlayground() holds the playground of the operation under <OperationProvider />, configured by the page's playground option.
'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
| Export | Does |
|---|---|
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
