Fumadocs

createGraphQL()

The GraphQL server instance.

GraphQL Server

The main config for Fumadocs GraphQL.

It should not be referenced in browser environments.

input

The GraphQL schemas to read from, either:

  • an array of file paths/URLs to SDL files.
  • a record of schema id -> input.

Record inputs accept SDL file paths/URLs, SDL text, introspection results, GraphQLSchema instances, and functions returning them.

Only schemas listed here can be loaded, other IDs passed to getSchema() throw an error.

import { createGraphQL } from '@fumadocs/graphql/server';

export const graphql = createGraphQL({
  input: ['./schema.graphql'],
});

You can pass an array of SDL files to merge them into a single schema, type extensions (e.g. extend type Query) are supported.

import { createGraphQL } from '@fumadocs/graphql/server';

export const graphql = createGraphQL({
  input: {
    api: ['./base.graphql', './orders.graphql'],
  },
});

examples

Example operations from GraphQL documents: file paths, URLs or GraphQL text.

import { createGraphQL } from '@fumadocs/graphql/server';

export const graphql = createGraphQL({
  input: ['./schema.graphql'],
  examples: ['./examples.graphql'],
});

An array applies to every schema. Each named operation becomes an example on the pages of root fields it selects, replacing the generated one:

examples.graphql
# The public profile of a customer.
query CustomerProfile($id: ID! = "cus_42") {
  customer(id: $id) {
    id
    name
  }
}
ExampleFrom
titlethe operation name, like Customer Profile for CustomerProfile
descriptionthe """ description of operation, or the # comments right above it, in Markdown
variablesdefault values of variables (left out of the query), or generated for required ones
responsegenerated from the selection

Operations must have unique names and be valid against the schema, otherwise the build fails.

Arguments of root fields must be variables of the same names, like customer(id: $id), so the playground can edit them.

disableCache

Disable caching of loaded schemas and examples. Useful during development when they change frequently.

import { createGraphQL } from '@fumadocs/graphql/server';

export const graphql = createGraphQL({
  input: ['./schema.graphql'],
  disableCache: true,
});

Loading Schemas Once

Pages receive the whole schema in their props, every page load, navigation and prefetch sends it again. For large schemas, leave it out:

lib/graphql.ts
import { createGraphQL } from '@fumadocs/graphql/server';

export const graphql = createGraphQL({
  // IDs end up in URLs, keep them URL-safe
  input: { store: './store.graphql' },
  includeSchema: false, 
});

On Next.js, Waku, React Router and Astro, <GraphQLPage /> loads the SDL of each schema once per browser session with loadSchema, it also runs during server rendering:

components/api-page.tsx
'use client';
import { preload } from 'react-dom';
import { createGraphQLPage } from '@fumadocs/graphql/ui';

export const GraphQLPage = createGraphQLPage({
  async loadSchema(id) {
    // `import.meta.env.SSR` on Waku, React Router and Astro
    if (typeof window === 'undefined') {
      // the browser starts downloading it with the page
      preload(`/api/graphql-schema/${id}`, { as: 'fetch', crossOrigin: 'anonymous' });
      const { graphql } = await import('@/lib/graphql');
      return (await graphql.getSchema(id)).sdl;
    }
    return (await fetch(`/api/graphql-schema/${id}`)).text();
  },
});

And serve schemas to the browser:

app/api/graphql-schema/[id]/route.ts
import { graphql } from '@/lib/graphql';

export async function GET(_req: Request, { params }: RouteContext<'/api/graphql-schema/[id]'>) {
  return new Response((await graphql.getSchema((await params).id)).sdl);
}

// for static exports
export async function generateStaticParams() {
  return Object.keys(await graphql.getSchemas()).map((id) => ({ id }));
}

On Tanstack Start, route loaders run on both sides, call a server function only when the browser doesn't have the schema yet:

src/routes/docs/$.tsx
import { createServerFn } from '@tanstack/react-start';
import { graphql } from '@/lib/graphql';

// schemas the browser has loaded
const schemas = new Map<string, string>();

const getSchema = createServerFn({ method: 'GET' })
  .validator((id: string) => id)
  .handler(async ({ data: id }) => (await graphql.getSchema(id)).sdl);

export const Route = createFileRoute('/docs/$')({
  component: Page,
  loader: async ({ params }) => {
    const data = await serverLoader({ data: params._splat?.split('/') ?? [] });
    if (data.type === 'graphql') {
      const { schema: id, payload } = data.props;
      payload.sdl = schemas.get(id) ?? (await getSchema({ data: id }));
    }
    return data;
  },
});

function Page() {
  const page = useFumadocsLoader(Route.useLoaderData());
  // the schema of the first page too
  if (page.type === 'graphql' && typeof window !== 'undefined')
    schemas.set(page.props.schema, page.props.payload.sdl!);
  // ...
}

Generating Pages

staticSource() and dynamicSource() generate pages for your loader(), they share the same options.

lib/source.ts
export const source = loader(
  {
    docs: docs.toFumadocsSource(),
    graphql: await graphql.staticSource({
      baseDir: 'graphql',
    }),
  },
  // ...
);

per

Customize the content of pages, default to item.

modecontentfile path
itemeach operation, and each named type with includeTypes{name}.mdx
filethe operations of a schema, and its named types with includeTypes{file_name}.mdx
customsee belowN/A

When set to custom, you can pass a function to fully customize the generation process:

import { graphql } from '@/lib/graphql';

await graphql.staticSource({
  per: 'custom',
  toPages(builder) {
    builder.create({
      type: 'operation',
      path: 'my-orders.mdx',
      info: { title: 'My Orders' },
      item: { kind: 'query', name: 'orders' },
    });
  },
});

groupBy

For per: 'item', customize how generated pages are grouped into folders, default to kind.

modefolders
kindby their kind, e.g. queries/, mutations/
noneno folders

You can also pass a function that returns the group name of an entry:

import { graphql } from '@/lib/graphql';

await graphql.staticSource({
  groupBy: (entry) => (entry.type === 'operation' ? 'operations' : 'types'),
});

includeOperations & includeTypes

Filter the operations (query/mutation/subscription fields) and named types to document.

  • includeOperations: a page per operation for per: 'item', default to true.
  • includeTypes: named types, their own pages for per: 'item' or in the page for per: 'file', default to false. Operation pages show the types they use either way.

Type pages show where the type is used, and link to each other and to operations.

import { graphql } from '@/lib/graphql';

await graphql.staticSource({
  // skip subscriptions
  includeOperations: (kind, field) => kind !== 'subscription',
  // document types, except internal ones
  includeTypes: (type) => !type.name.startsWith('Internal'),
});

name & slugify

Customize the file name of generated pages.

GraphQL names are already URL-safe, hence the original name (including its case) is kept by default.

import { graphql } from '@/lib/graphql';

await graphql.staticSource({
  per: 'item',
  name: (entry) => entry.item.name.toLowerCase(),
});

slugify converts group names (and item names by default) into URL-friendly segments.

meta

Generate meta.json files to customize the page tree, disabled by default.

folderStylepage tree
foldera folder per group (default)
separatora separator per group, with pages under it
import { graphql } from '@/lib/graphql';

await graphql.staticSource({
  meta: { folderStyle: 'separator' },
});

Markdown for LLMs

page.data.getText() returns the items of a page in Markdown: operations with their signature, arguments, examples and the SDL of types they use, named types with their SDL and usages. For example, in docsLlms:

import { llms } from 'fumadocs-core/source';
import { source } from '@/lib/source';

export const docsLlms = llms(source, {
  renderPage: async (page) => {
    if (page.type === 'graphql') {
      return `# ${page.data.title}\n\n${await page.data.getText('processed')}`;
    }

    // your original flow below...
  },
});

Loader Plugin

loaderPlugin() decorates GraphQL pages in your page tree:

  • a query/mutation/subscription badge per operation page.
  • a strikethrough on deprecated operations.
lib/source.ts
export const source = loader(
  // ...
  {
    baseUrl: '/docs',
    plugins: [graphql.loaderPlugin()],
  },
);

How is this guide?

Last updated on