Skip to main content
Plugins in GraphDoc control the content displayed on every page of your generated documentation. A plugin is an object that implements the PluginInterface, allowing you to customize navigation sections, document sections, headers, and assets.

Understanding the PluginInterface

The PluginInterface (defined in lib/interface.d.ts:15-120) specifies four optional methods that you can implement:
export interface PluginInterface {
  getNavigations?: (
    buildForType?: string
  ) => NavigationSectionInterface[] | PromiseLike<NavigationSectionInterface[]>;

  getDocuments?: (
    buildForType?: string
  ) => DocumentSectionInterface[] | PromiseLike<DocumentSectionInterface[]>;

  getHeaders?: (buildForType?: string) => string[] | PromiseLike<string[]>;

  getAssets?: () => string[] | PromiseLike<string[]>;
}
Navigation sections appear in the sidebar of your documentation:
export interface NavigationSectionInterface {
  title: string;
  items: NavigationItemInterface[];
}

export interface NavigationItemInterface {
  href: string;
  text: string;
  isActive: boolean;
}

DocumentSectionInterface

Document sections are rendered in the main content area:
export interface DocumentSectionInterface {
  title: string;
  description: string;
}

Creating your first plugin

You can create a plugin as either a plain object or a constructor function. If you use a constructor, it receives three parameters:
1

Create the plugin class

Extend the Plugin base class and implement PluginInterface:
import { PluginInterface, NavigationSectionInterface } from "graphdoc/lib/interface";
import { Plugin, NavigationSection, NavigationItem } from "graphdoc/lib/utility";

export default class MyCustomPlugin extends Plugin implements PluginInterface {
  constructor(document, projectPackage, graphdocPackage) {
    super(document, projectPackage, graphdocPackage);
  }
}
The constructor parameters are:
  • document: The full result of the GraphQL introspection query (type: Schema)
  • projectPackage: Content of your project’s package.json (or config file)
  • graphdocPackage: Content of GraphDoc’s package.json
2

Implement plugin methods

Add one or more of the four plugin methods based on what you want to customize:
getNavigations(buildForType?: string): NavigationSectionInterface[] {
  return [
    new NavigationSection("Custom Section", [
      new NavigationItem("Example", "./example.html", false)
    ])
  ];
}

getDocuments(buildForType?: string): DocumentSectionInterface[] {
  return [
    new DocumentSection("Custom Title", "<p>HTML content here</p>")
  ];
}

getHeaders(buildForType?: string): string[] {
  return ['<link href="./assets/custom.css" rel="stylesheet">'];
}

getAssets(): string[] {
  return [resolve(__dirname, "assets/custom.css")];
}
3

Use the plugin

Reference your plugin via command line or package.json:
graphdoc -p ./lib/plugins/my-custom-plugin \
  -s ./schema.json \
  -o ./doc/schema
For performance reasons, all plugins receive references to the same objects. Do not modify document, projectPackage, or graphdocPackage directly unless you intend to affect other plugins.

Real plugin examples

Let’s examine actual plugins from the GraphDoc source code to understand different patterns. The NavigationEnum plugin (from plugins/navigation.enum.ts:9-32) shows how to add enum types to the sidebar:
export default class NavigationEnums extends Plugin implements PluginInterface {
  getTypes(buildForType: string): NavigationItemInterface[] {
    return this.document.types
      .filter(type => type.kind === ENUM)
      .map(
        type =>
          new NavigationItem(
            type.name,
            this.url(type),
            type.name === buildForType
          )
      );
  }

  getNavigations(buildForType: string) {
    const types: NavigationItemInterface[] = this.getTypes(buildForType);

    if (types.length === 0) {
      return [];
    }

    return [new NavigationSection("Enums", types)];
  }
}
  • Filter schema types: Use this.document.types.filter() to find specific kinds
  • Generate URLs: Use this.url(type) from the base Plugin class to create proper links
  • Mark active items: Compare type.name === buildForType to highlight the current page
  • Return empty arrays: When no items exist, return [] to avoid rendering empty sections

Document content plugin example

The DocumentSchema plugin (from plugins/document.schema/index.ts:28-99) generates GraphQL schema definitions as HTML:
export default class SchemaPlugin extends Plugin implements PluginInterface {
  private html: HTML;

  getHeaders(): string[] {
    return [
      '<link href="https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700" rel="stylesheet">',
      '<link type="text/css" rel="stylesheet" href="./assets/code.css" />',
      '<script src="./assets/line-link.js"></script>'
    ];
  }

  getAssets() {
    return [
      resolve(__dirname, "assets/code.css"),
      resolve(__dirname, "assets/line-link.js")
    ];
  }

  getDocuments(buildForType?: string): DocumentSectionInterface[] {
    this.html = new HTML();
    const code = this.code(buildForType);

    if (code) {
      return [
        new DocumentSection("GraphQL Schema definition", this.html.code(code))
      ];
    }

    return [];
  }

  code(buildForType?: string): string {
    if (!buildForType) {
      return this.schema(this.document);
    }

    const type = this.document.types.find(
      eachType => eachType.name === buildForType
    );

    if (type) {
      switch (type.kind) {
        case SCALAR:
          return this.scalar(type);
        case OBJECT:
          return this.object(type);
        case INTERFACE:
          return this.interfaces(type);
        // ... more cases
      }
    }

    throw new TypeError("Unexpected type: " + buildForType);
  }
}
  • Coordinate headers and assets: Use getHeaders() to reference assets that getAssets() copies
  • Use helper classes: The HTML class (from lib/utility/html.ts:4-88) provides methods for generating syntax-highlighted code
  • Handle different contexts: Check if buildForType is provided to determine if rendering the index or a specific type page
  • Generate rich content: Return HTML strings in the description field of DocumentSection

Dependency tracking plugin example

The RequireByPlugin (from plugins/document.require-by/index.ts:20-158) tracks which types depend on each other:
export default class RequireByPlugin extends Plugin implements PluginInterface {
  requireBy: Map<string, SchemaType[]>;

  constructor(
    public document: Schema,
    public projectPackage: any,
    public graphdocPackage: any
  ) {
    super(document, projectPackage, graphdocPackage);

    this.requireBy = new Map();

    if (Array.isArray(document.types)) {
      document.types.forEach((type: SchemaType) => {
        this.getDependencies(type).forEach((curr: string) => {
          const deps = this.requireBy.get(curr) || [];
          deps.push(type);
          this.requireBy.set(curr, deps);
        });
      });
    }
  }

  getDependencies(type: SchemaType): string[] {
    const deps: string[] = [];

    if (Array.isArray(type.fields) && type.fields.length > 0) {
      type.fields.forEach(field => {
        deps.push(getTypeOf(field.type).name);

        if (Array.isArray(field.args) && field.args.length > 0) {
          field.args.forEach(arg => {
            deps.push(getTypeOf(arg.type).name);
          });
        }
      });
    }

    return deps;
  }

  getDocuments(buildForType?: string): DocumentSectionInterface[] {
    if (!buildForType) {
      return [];
    }

    const requireBy = this.requireBy.get(buildForType);

    if (!Array.isArray(requireBy) || requireBy.length === 0) {
      return [
        {
          title: "Required by",
          description:
            '<div class="require-by anyone">' +
            "This element is not required by anyone" +
            "</div>"
        }
      ];
    }

    const used = new Set();

    return [
      {
        title: "Required by",
        description:
          '<ul class="require-by">' +
          requireBy
            .filter(t => {
              return used.has(t.name) ? false : used.add(t.name);
            })
            .map(t => this.getDescription(t))
            .join("") +
          "</ul>"
      }
    ];
  }
}
  • Pre-compute in constructor: Build lookup maps during initialization for better performance
  • Use utility functions: The getTypeOf() helper (from lib/utility/introspection.ts) unwraps nested types like [Type!]!
  • Context-specific rendering: Only show “Required by” sections on type pages, not the index
  • Deduplicate results: Use a Set to avoid showing duplicate dependencies

Composing multiple plugins

The default plugin (from plugins/default.ts:19-54) demonstrates how to combine multiple plugins:
export default class NavigationDirectives extends Plugin
  implements PluginInterface {
  plugins: PluginInterface[];

  constructor(document: Schema, graphdocPackage: any, projectPackage: any) {
    super(document, graphdocPackage, projectPackage);
    this.plugins = [
      new NavigationSchema(document, graphdocPackage, projectPackage),
      new NavigationScalar(document, graphdocPackage, projectPackage),
      new NavigationEnum(document, graphdocPackage, projectPackage),
      new NavigationInterfaces(document, graphdocPackage, projectPackage),
      new NavigationUnion(document, graphdocPackage, projectPackage),
      new NavigationObject(document, graphdocPackage, projectPackage),
      new NavigationInput(document, graphdocPackage, projectPackage),
      new NavigationDirective(document, graphdocPackage, projectPackage),
      new DocumentSchema(document, graphdocPackage, projectPackage),
      new RequireByPlugin(document, graphdocPackage, projectPackage)
    ];
  }

  getNavigations(buildForType?: string): Promise<NavigationSectionInterface[]> {
    return Plugin.collectNavigations(this.plugins, buildForType);
  }

  getDocuments(buildForType?: string): Promise<DocumentSectionInterface[]> {
    return Plugin.collectDocuments(this.plugins, buildForType);
  }

  getHeaders(buildForType?: string): Promise<string[]> {
    return Plugin.collectHeaders(this.plugins, buildForType);
  }

  getAssets(): Promise<string[]> {
    return Plugin.collectAssets(this.plugins);
  }
}
The base Plugin class provides static collection methods (from lib/utility/plugin.ts:32-86) that gather results from all plugins:
static async collectNavigations(
  plugins: PluginInterface[],
  buildForType?: string
): Promise<NavigationSectionInterface[]> {
  const navigationCollection = await Promise.all<NavigationSectionInterface[]>(
    plugins.map(plugin => {
      return plugin.getNavigations
        ? plugin.getNavigations(buildForType)
        : (null as any);
    })
  );

  return Plugin.collect(navigationCollection);
}

Using the Plugin base class

The Plugin base class (from lib/utility/plugin.ts:19-148) provides helpful properties and methods:

Available properties

class Plugin {
  queryType: SchemaType | null = null;
  mutationType: SchemaType | null = null;
  subscriptionType: SchemaType | null = null;
  typeMap: { [name: string]: SchemaType } = {};
  directiveMap: { [name: string]: Directive } = {};
  document: Schema;
  projectPackage: any;
  graphdocPackage: any;
}
The typeMap and directiveMap are automatically populated in the constructor, allowing quick lookups by name.

The url() method

Generate proper URLs for types using the url() method:
url(type: DeepTypeRef | TypeRef | Description): string {
  return url.resolve(
    this.projectPackage.graphdoc.baseUrl,
    getFilenameOf(type)
  );
}

Helper classes

GraphDoc provides utility classes for constructing plugin results:
import {
  NavigationSection,
  NavigationItem,
  DocumentSection
} from "graphdoc/lib/utility";

// Create navigation items
const navSection = new NavigationSection("My Section", [
  new NavigationItem("Label", "./page.html", false)
]);

// Create document sections
const docSection = new DocumentSection("Title", "<p>HTML content</p>");

Advanced techniques

Async operations

All plugin methods can return Promises:
async getDocuments(buildForType?: string): Promise<DocumentSectionInterface[]> {
  const data = await fetchExternalData();
  return [
    new DocumentSection("External Data", formatData(data))
  ];
}

Accessing configuration

Use this.projectPackage.graphdoc to access custom configuration:
{
  "graphdoc": {
    "endpoint": "http://localhost:8080/graphql",
    "output": "./docs",
    "myCustomConfig": {
      "option1": "value1"
    }
  }
}
getDocuments(buildForType?: string): DocumentSectionInterface[] {
  const config = this.projectPackage.graphdoc.myCustomConfig;
  // Use config.option1
}

Generating HTML

Use the HTML class for syntax-highlighted code:
import { HTML } from "graphdoc/lib/utility";

const html = new HTML();
const code = html.line(
  html.keyword("type") + " " + html.identifier(type) + " {"
) + html.line("}");

const wrapped = html.code(code);

Plugin export formats

GraphDoc supports multiple export formats:
export default class MyPlugin {
  constructor(schema, projectPackage, graphdocPackage) {}

  getAssets() {
    return [];
  }
}

Best practices

Return empty arrays

When no content is available, return [] instead of null to avoid rendering empty sections.

Leverage base class

Extend the Plugin base class to access pre-populated type maps and helper methods.

Check buildForType

Use the buildForType parameter to render different content for the index vs. type pages.

Coordinate assets

Ensure paths in getHeaders() match filenames returned by getAssets().
Assets are copied to the assets/ directory in the output folder. Reference them as ./assets/filename.ext in your headers.

Build docs developers (and LLMs) love