Repository: graphql-markdown/graphql-markdown
Pull request #1279 (2024-02-10 13:58:19)

:sparkle: top level grouping for api and types
# Description

Implements #1265 splitting schema definition into 2 groups: API (executable) and Types (definition/system).

New option `printTypeOptions.useApiGroup` set to `true` by default. It can be disable through config file or cli flag `--noApiGroup`.


![Screenshot from 2024-02-10 16-37-28](https://github.com/graphql-markdown/graphql-markdown/assets/324670/d339e140-4738-4df6-8631-d1e1d4fe9954)

# Checklist

- [x] My changes follow the [contributing guidelines](https://github.com/graphql-markdown/graphql-markdown/blob/main/CONTRIBUTING.md) of this project.
- [x] I have performed a self-review of my code.
- [x] I have made corresponding changes to the documentation.
- [x] I have added tests that prove my fix is effective or that my changes work.
- [x] New and existing unit tests pass locally with my changes.


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=graphql-markdown_graphql-markdown&pullRequest=1279) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=graphql-markdown_graphql-markdown&pullRequest=1279&resolved=false&inNewCodePeriod=true)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=graphql-markdown_graphql-markdown&pullRequest=1279&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2.6% Duplication on New Code](https://sonarcloud.io/component_measures?id=graphql-markdown_graphql-markdown&pullRequest=1279&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=graphql-markdown_graphql-markdown&pullRequest=1279)


codesee-maps[bot]: <details open>
<summary><b>👇 <a href="https://app.codesee.io/r/reviews?pr=1279&src=https%3A%2F%2Fgithub.com%2Fgraphql-markdown%2Fgraphql-markdown">Click on the image</a> for a new way to code review</b>
</summary>

####

[![Review these changes using an interactive CodeSee Map](https://s3.us-east-2.amazonaws.com/maps.codesee.io/images/github/graphql-markdown/graphql-markdown/1279/ef62657a/55b90c1853003a850ba3f72342e2fe66dd98a5bf.svg)](https://app.codesee.io/r/reviews?pr=1279&src=https%3A%2F%2Fgithub.com%2Fgraphql-markdown%2Fgraphql-markdown)

#### Legend
<img src="https://app.codesee.io/Legend_v3.png" alt="CodeSee Map legend" width="220" height="156">
</details>
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=graphql-markdown_graphql-markdown&pullRequest=1279) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=graphql-markdown_graphql-markdown&pullRequest=1279&resolved=false&inNewCodePeriod=true)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=graphql-markdown_graphql-markdown&pullRequest=1279&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2.6% Duplication on New Code](https://sonarcloud.io/component_measures?id=graphql-markdown_graphql-markdown&pullRequest=1279&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=graphql-markdown_graphql-markdown&pullRequest=1279)


github-actions[bot]: 
<!--
  0 failure: 
  1 warning:  This PR modified ...
  
  
  DangerID: danger-id-Danger;
-->


<table>
  <thead>
    <tr>
      <th width="50"></th>
      <th width="100%" data-danger-table="true">Warnings</th>
    </tr>
  </thead>
  <tbody><tr>
      <td>:warning:</td>
      <td>

  This PR modified some Jest snapshot file/s:
 - packages/printer-legacy/tests/unit/__snapshots__/link.test.ts.snap
  </td>
    </tr>
  </tbody>
</table>



<p align="right">
  Generated by :no_entry_sign: <a href="https://danger.systems/js">dangerJS</a> against 55b90c1853003a850ba3f72342e2fe66dd98a5bf
</p>

coveralls: 
[![Coverage Status](https://coveralls.io/builds/65621870/badge)](https://coveralls.io/builds/65621870)

coverage: 94.439% (-0.1%) from 94.554%
when pulling **55b90c1853003a850ba3f72342e2fe66dd98a5bf on feat/1265-separate-api-from-types**
into **3f5494afd2102db9d2c349f4cf005f832bf196d3 on main**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/65621869/badge)](https://coveralls.io/builds/65621869)

coverage: 94.439% (-0.1%) from 94.554%
when pulling **55b90c1853003a850ba3f72342e2fe66dd98a5bf on feat/1265-separate-api-from-types**
into **3f5494afd2102db9d2c349f4cf005f832bf196d3 on main**.

sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=graphql-markdown_graphql-markdown&pullRequest=1279) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=graphql-markdown_graphql-markdown&pullRequest=1279&resolved=false&inNewCodePeriod=true)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=graphql-markdown_graphql-markdown&pullRequest=1279&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2.4% Duplication on New Code](https://sonarcloud.io/component_measures?id=graphql-markdown_graphql-markdown&pullRequest=1279&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=graphql-markdown_graphql-markdown&pullRequest=1279)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=graphql-markdown_graphql-markdown&pullRequest=1279) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=graphql-markdown_graphql-markdown&pullRequest=1279&resolved=false&inNewCodePeriod=true)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=graphql-markdown_graphql-markdown&pullRequest=1279&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2.4% Duplication on New Code](https://sonarcloud.io/component_measures?id=graphql-markdown_graphql-markdown&pullRequest=1279&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=graphql-markdown_graphql-markdown&pullRequest=1279)



Patch:
diff --git a/Earthfile b/Earthfile
index 6211f6d1..fec57dd0 100644
--- a/Earthfile
+++ b/Earthfile
@@ -104,7 +104,7 @@ build-examples:
   RUN mkdir $TARGET
   DO +GQLMD --options="--homepage data/anilist.md --schema https://graphql.anilist.co/ --base . --link /${TARGET}/default --force --pretty --deprecated group"
   RUN mv docs ./$TARGET/default
-  DO +GQLMD --id="schema_with_grouping" --options="--homepage data/groups.md --schema data/schema_with_grouping.graphql --groupByDirective @doc(category|=Common) --base . --link /${TARGET}/group-by --skip @noDoc --index --noParentType --noRelatedType --deprecated group"
+  DO +GQLMD --id="schema_with_grouping" --options="--homepage data/groups.md --schema data/schema_with_grouping.graphql --groupByDirective @doc(category|=Common) --base . --link /${TARGET}/group-by --skip @noDoc --index --noParentType --noRelatedType --deprecated group --noApiGroup"
   RUN mv docs ./$TARGET/group-by
   SAVE ARTIFACT ./$TARGET
 
diff --git a/docs/settings.md b/docs/settings.md
index 8a3fcde5..9c98d1d3 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -224,21 +224,23 @@ It only applies to types with a location compatible with the directive, i.e. if
 Use these options to toggle type information rendered on pages:
 
 - `codeSection`: display type code section.
-- `parentTypePrefix`: prefix field names with parent type name.
-- `relatedTypeSection`: display related type sections.
-- `typeBadges`: add field type attributes badges.
 - `deprecated`: option for displaying deprecated entities (fields, values, operations).
   - `default`: deprecated entities are displayed with other entities.
   - `group`: deprecated entities are grouped together.
   - `skip`: deprecated entities are not displayed (same as [`skipDocDirective`](#skipdocdirective)).
+- `parentTypePrefix`: prefix field names with parent type name.
+- `relatedTypeSection`: display related type sections.
+- `typeBadges`: add field type attributes badges.
+- `useApiGroup`: split entities in `API` group (executable types) and `Types` group (system types).
 
 | Setting                               | CLI flag                | Default   |
 | ------------------------------------- | ----------------------- | --------- |
 | `printTypeOptions.codeSection`        | `--noCode`              | `true`    |
+| `printTypeOptions.deprecated`         | `--deprecated <option>` | `default` |
 | `printTypeOptions.parentTypePrefix`   | `--noParentType`        | `true`    |
 | `printTypeOptions.relatedTypeSection` | `--noRelatedType`       | `true`    |
 | `printTypeOptions.typeBadges`         | `--noTypeBadges`        | `true`    |
-| `printTypeOptions.deprecated`         | `--deprecated <option>` | `default` |
+| `printTypeOptions.useApiGroup`        | `--noApiGroup`          | `true`    |
 
 <br/>
 
@@ -254,10 +256,11 @@ plugins: [
         // highlight-start
         printTypeOptions: {
           codeSection: false, // disable code section, same as CLI flag --noCode
+          deprecated: "group", // group deprecated entities, same as CLI flag --deprecated group
           parentTypePrefix: false, // disable parent prefix, same as CLI flag --noParentType
           relatedTypeSection: false, // disable related type sections, same as CLI flag --noRelatedType
           typeBadges: false, // disable type attribute badges, same as CLI flag --noTypeBadges
-          deprecated: "group", // group deprecated entities, same as CLI flag --deprecated group
+          useApiGroup: false, // disable type API grouping, same as CLI flag --noApiGroup
         },
         // highlight-end
         loaders: {
diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts
index b01cc816..0becdd4b 100644
--- a/packages/core/src/config.ts
+++ b/packages/core/src/config.ts
@@ -75,6 +75,7 @@ export const DEFAULT_OPTIONS: Required<
     parentTypePrefix: true,
     relatedTypeSection: true,
     typeBadges: true,
+    useApiGroup: true,
   },
   rootPath: "./docs",
   schema: "./schema.graphql",
@@ -289,6 +290,9 @@ export const getPrintTypeOptions = (
     typeBadges:
       (!cliOpts?.noTypeBadges && configOptions?.typeBadges) ??
       DEFAULT_OPTIONS.printTypeOptions.typeBadges!,
+    useApiGroup:
+      (!cliOpts?.noApiGroup && configOptions?.useApiGroup) ??
+      DEFAULT_OPTIONS.printTypeOptions.useApiGroup!,
   } as Required<ConfigPrintTypeOptions>;
 };
 
diff --git a/packages/core/src/generator.ts b/packages/core/src/generator.ts
index eaeb0c02..5ed4c1d9 100644
--- a/packages/core/src/generator.ts
+++ b/packages/core/src/generator.ts
@@ -112,6 +112,7 @@ export const generateDocFromSchema = async ({
   const renderer = new Renderer(printer, outputDir, baseURL, groups, prettify, {
     ...docOptions,
     deprecated: printTypeOptions.deprecated,
+    useApiGroup: printTypeOptions.useApiGroup,
   });
 
   const pages = await Promise.all(
diff --git a/packages/core/src/renderer.ts b/packages/core/src/renderer.ts
index 44911077..0917e896 100644
--- a/packages/core/src/renderer.ts
+++ b/packages/core/src/renderer.ts
@@ -1,17 +1,16 @@
 import type {
   Category,
-  ConfigDocOptions,
   Maybe,
   MDXString,
   Printer,
+  RendererDocOptions,
   SchemaEntitiesGroupMap,
   SchemaEntity,
-  TypeDeprecatedOption,
 } from "@graphql-markdown/types";
 
 import { basename, join, relative, normalize } from "node:path";
 
-import { isDeprecated } from "@graphql-markdown/graphql";
+import { isApiType, isDeprecated } from "@graphql-markdown/graphql";
 
 import {
   copyFile,
@@ -41,7 +40,7 @@ export class Renderer {
   outputDir: string;
   baseURL: string;
   prettify: boolean;
-  options: Maybe<ConfigDocOptions & { deprecated: TypeDeprecatedOption }>;
+  options: Maybe<RendererDocOptions>;
 
   private readonly printer: Printer;
 
@@ -51,7 +50,7 @@ export class Renderer {
     baseURL: string,
     group: Maybe<SchemaEntitiesGroupMap>,
     prettify: boolean,
-    docOptions: Maybe<ConfigDocOptions & { deprecated: TypeDeprecatedOption }>,
+    docOptions: Maybe<RendererDocOptions>,
   ) {
     this.printer = printer;
     this.group = group;
@@ -64,8 +63,12 @@ export class Renderer {
   async generateCategoryMetafile(
     category: string,
     dirPath: string,
-    sidebarPosition: number = SidebarPosition.FIRST,
+    sidebarPosition?: number,
     styleClass?: string,
+    options: { collapsible: boolean; collapsed: boolean } = {
+      collapsible: true,
+      collapsed: true,
+    },
   ): Promise<void> {
     const filePath = join(dirPath, CATEGORY_YAML);
 
@@ -82,9 +85,13 @@ export class Renderer {
         : `\n  type: generated-index\n  title: '${label} overview'`;
     const className =
       typeof styleClass === "string" ? `className: ${styleClass}\n` : "";
+    const position =
+      typeof sidebarPosition === "number"
+        ? sidebarPosition
+        : SidebarPosition.FIRST;
     await saveFile(
       filePath,
-      `label: ${label}\nposition: ${sidebarPosition}\n${className}link: ${link}\n`,
+      `label: ${label}\nposition: ${position}\n${className}link: ${link}\ncollapsible: ${options.collapsible}\ncollapsed: ${options.collapsed}\n`,
     );
   }
 
@@ -95,11 +102,19 @@ export class Renderer {
   ): Promise<string> {
     let dirPath = this.outputDir;
 
-    if (
-      this.options &&
-      this.options.deprecated === "group" &&
-      isDeprecated(type)
-    ) {
+    if (this.options?.useApiGroup === true) {
+      const typeCat = isApiType(type) ? "api" : "types";
+      dirPath = join(dirPath, slugify(typeCat));
+      await this.generateCategoryMetafile(
+        typeCat,
+        dirPath,
+        undefined,
+        "graphql-markdown-api-section",
+        { collapsible: false, collapsed: false },
+      );
+    }
+
+    if (this.options?.deprecated === "group" && isDeprecated(type)) {
       dirPath = join(dirPath, slugify("deprecated"));
       await this.generateCategoryMetafile(
         "deprecated",
diff --git a/packages/docusaurus/src/index.ts b/packages/docusaurus/src/index.ts
index ce4aa23a..a025305d 100644
--- a/packages/docusaurus/src/index.ts
+++ b/packages/docusaurus/src/index.ts
@@ -53,6 +53,7 @@ export default function pluginGraphQLDocGenerator(
           "-h, --homepage <homepage>",
           "File location for doc landing page",
         )
+        .option("--noApiGroup", "Disable API grouping for types")
         .option("--noCode", "Disable code section for types")
         .option("--noParentType", "Disable parent type name as field prefix")
         .option("--noRelatedType", "Disable related types sections")
diff --git a/packages/graphql/src/directive.ts b/packages/graphql/src/directive.ts
index dfde532d..39a464b9 100644
--- a/packages/graphql/src/directive.ts
+++ b/packages/graphql/src/directive.ts
@@ -13,11 +13,13 @@ import type {
   CustomDirectiveOptions,
   Maybe,
   SchemaMap,
+  GraphQLDirective,
 } from "@graphql-markdown/types";
 
 import { isEmpty } from "@graphql-markdown/utils";
 
 import { getDirective } from "./introspection";
+import { DirectiveLocation } from "graphql";
 
 /**
  * Wildcard `*` character for matching any directive name.
@@ -272,3 +274,39 @@ export const getConstDirectiveMap = (
     {},
   );
 };
+
+/**
+ * Checks if a directive is executable (related to operations).
+ *
+ * @param type - a GraphQL directive.
+ *
+ */
+export const executableDirectiveLocation = (
+  directive: GraphQLDirective,
+): boolean => {
+  return [
+    DirectiveLocation.QUERY,
+    DirectiveLocation.MUTATION,
+    DirectiveLocation.SUBSCRIPTION,
+    DirectiveLocation.FIELD,
+    DirectiveLocation.FRAGMENT_DEFINITION,
+    DirectiveLocation.FRAGMENT_SPREAD,
+    DirectiveLocation.INLINE_FRAGMENT,
+    DirectiveLocation.VARIABLE_DEFINITION,
+  ].some((item) => {
+    return directive.locations.includes(item);
+  });
+};
+
+/**
+ * Checks if a directive is system (related to schema definition).
+ *
+ * @param type - a GraphQL directive.
+ *
+ */
+export const typeSystemDirectiveLocation = (
+  directive: GraphQLDirective,
+): boolean => {
+  // we assume that if not executable then it is a system directive
+  return !executableDirectiveLocation(directive);
+};
diff --git a/packages/graphql/src/guard.ts b/packages/graphql/src/guard.ts
index f686db03..93a885c9 100644
--- a/packages/graphql/src/guard.ts
+++ b/packages/graphql/src/guard.ts
@@ -9,6 +9,9 @@ import type {
   GraphQLOperationType,
   DeprecatedType,
 } from "@graphql-markdown/types";
+import { isDirectiveType } from "@graphql-markdown/graphql";
+
+import { executableDirectiveLocation } from "./directive";
 
 export {
   isDirective as isDirectiveType,
@@ -85,3 +88,26 @@ export const isDeprecated = <T>(obj: T): obj is DeprecatedType<T> => {
 export const isOperation = (type: unknown): type is GraphQLOperationType => {
   return typeof type === "object" && type !== null && "type" in type;
 };
+
+/**
+ * Checks if a type belongs to API (operation related).
+ *
+ * @param type - a GraphQL type.
+ *
+ */
+export const isApiType = (type: unknown): boolean => {
+  if (isOperation(type)) {
+    return true;
+  }
+  return isDirectiveType(type) && executableDirectiveLocation(type);
+};
+
+/**
+ * Checks if a type belongs to schema (schema type definition excluding operations related types).
+ *
+ * @param type - a GraphQL type.
+ *
+ */
+export const isSystemType = (type: unknown): boolean => {
+  return !isApiType(type);
+};
diff --git a/packages/printer-legacy/src/badge.ts b/packages/printer-legacy/src/badge.ts
index 2d8ed041..149eed10 100644
--- a/packages/printer-legacy/src/badge.ts
+++ b/packages/printer-legacy/src/badge.ts
@@ -17,7 +17,7 @@ import {
   isNonNullType,
 } from "@graphql-markdown/graphql";
 
-import { getLinkCategory } from "./link";
+import { getCategoryLocale } from "./link";
 import { getGroup } from "./group";
 
 export const DEFAULT_CSS_CLASSNAME = "badge--secondary" as const;
@@ -52,7 +52,7 @@ export const getTypeBadges = (
     badges.push({ text: "list", classname: DEFAULT_CSS_CLASSNAME } as Badge);
   }
 
-  const category = getLinkCategory(getNamedType(rootType));
+  const category = getCategoryLocale(getNamedType(rootType));
   if (category) {
     badges.push({ text: category, classname: DEFAULT_CSS_CLASSNAME } as Badge);
   }
diff --git a/packages/printer-legacy/src/const/options.ts b/packages/printer-legacy/src/const/options.ts
index 2443741c..54b66dcd 100644
--- a/packages/printer-legacy/src/const/options.ts
+++ b/packages/printer-legacy/src/const/options.ts
@@ -25,6 +25,7 @@ export const PRINT_TYPE_DEFAULT_OPTIONS: Required<PrinterConfigPrintTypeOptions>
     parentTypePrefix: true,
     relatedTypeSection: true,
     typeBadges: true,
+    useApiGroup: true,
   };
 
 export const DEFAULT_OPTIONS: Required<
diff --git a/packages/printer-legacy/src/group.ts b/packages/printer-legacy/src/group.ts
index b9c18616..ed930604 100644
--- a/packages/printer-legacy/src/group.ts
+++ b/packages/printer-legacy/src/group.ts
@@ -14,7 +14,13 @@ export const getGroup = (
   groups: Maybe<SchemaEntitiesGroupMap>,
   typeCategory: Maybe<SchemaEntity>,
 ): string => {
-  if (typeof type !== "object" || type === null || !groups || !typeCategory) {
+  if (
+    typeof type !== "object" ||
+    type === null ||
+    typeof groups !== "object" ||
+    groups === null ||
+    !typeCategory
+  ) {
     return "";
   }
 
diff --git a/packages/printer-legacy/src/link.ts b/packages/printer-legacy/src/link.ts
index 60975652..67337dde 100644
--- a/packages/printer-legacy/src/link.ts
+++ b/packages/printer-legacy/src/link.ts
@@ -5,6 +5,7 @@ import type {
   Maybe,
   PrintLinkOptions,
   SchemaEntity,
+  TypeDeprecatedOption,
   TypeLink,
   TypeLocale,
 } from "@graphql-markdown/types";
@@ -12,6 +13,7 @@ import type {
 import {
   getNamedType,
   getTypeName,
+  isApiType,
   isDeprecated,
   isDirectiveType,
   isEnumType,
@@ -32,7 +34,7 @@ import { getGroup } from "./group";
 import { DEPRECATED, ROOT_TYPE_LOCALE } from "./const/strings";
 import { hasPrintableDirective } from "./common";
 
-export const getLinkCategory = (type: unknown): Maybe<TypeLocale> => {
+export const getCategoryLocale = (type: unknown): Maybe<TypeLocale> => {
   switch (true) {
     case isDirectiveType(type):
       return ROOT_TYPE_LOCALE.DIRECTIVE;
@@ -54,6 +56,31 @@ export const getLinkCategory = (type: unknown): Maybe<TypeLocale> => {
   return undefined;
 };
 
+export const getLinkCategoryFolder = (
+  type: unknown,
+  operationLocale?: Maybe<TypeLocale>,
+): Maybe<string> => {
+  const categoryLocale = getCategoryLocale(type);
+
+  if (!categoryLocale) {
+    return undefined;
+  }
+
+  // special case for relation map
+  if (categoryLocale === ROOT_TYPE_LOCALE.OPERATION) {
+    if (!operationLocale) {
+      return undefined;
+    }
+    return typeof operationLocale === "object"
+      ? operationLocale.plural
+      : operationLocale;
+  }
+
+  return typeof categoryLocale === "object"
+    ? categoryLocale.plural
+    : categoryLocale;
+};
+
 export const hasOptionWithAttributes = (options: PrintLinkOptions): boolean => {
   return "withAttributes" in options && options.withAttributes === true;
 };
@@ -67,6 +94,17 @@ export const hasOptionParentType = (options: PrintLinkOptions): boolean => {
   );
 };
 
+export const getLinkApiGroupFolder = (type: unknown): string => {
+  return isApiType(type) ? "api" : "types";
+};
+
+export const getLinkDeprecatedFolder = (
+  type: unknown,
+  option: Maybe<TypeDeprecatedOption>,
+): string => {
+  return option === "group" && isDeprecated(type) ? DEPRECATED : "";
+};
+
 export const toLink = (
   type: unknown,
   name: string,
@@ -78,58 +116,42 @@ export const toLink = (
     url: "#",
   };
 
-  if (typeof type !== "object") {
-    return fallback;
-  }
-
-  if (typeof options !== "undefined" && !hasPrintableDirective(type, options)) {
+  if (
+    typeof type !== "object" ||
+    (typeof options !== "undefined" && !hasPrintableDirective(type, options))
+  ) {
     return fallback;
   }
 
   const graphQLNamedType = getNamedType(type as Maybe<GraphQLType>);
 
-  let category = getLinkCategory(graphQLNamedType);
+  const category = getLinkCategoryFolder(graphQLNamedType, operation);
 
   if (!category || !graphQLNamedType) {
     return fallback;
   }
 
-  // special case for relation map
-  if (category === ROOT_TYPE_LOCALE.OPERATION) {
-    if (!operation) {
-      return fallback;
-    }
-    category = operation;
-  }
-
-  if (typeof category === "object") {
-    category = category.plural;
-  }
-
   const text = graphQLNamedType.name || graphQLNamedType.toString();
-  const deprecated =
-    typeof options !== "undefined" &&
-    "deprecated" in options &&
-    options.deprecated === "group" &&
-    isDeprecated(type)
-      ? DEPRECATED
-      : "";
-  const group =
-    "groups" in options && options.groups
-      ? getGroup(type, options.groups, category as SchemaEntity)
-      : "";
+  const deprecatedFolder = options.deprecated
+    ? getLinkDeprecatedFolder(type, options.deprecated)
+    : "";
+  const groupFolder = options.groups
+    ? getGroup(type, options.groups, category as SchemaEntity)
+    : "";
+  const apiGroupFolder = options.useApiGroup ? getLinkApiGroupFolder(type) : "";
 
   const url = pathUrl.join(
     options.basePath,
-    deprecated,
-    group,
+    deprecatedFolder,
+    apiGroupFolder,
+    groupFolder,
     category,
     slugify(text),
   );
 
   return {
-    text: text,
-    url: url,
+    text,
+    url,
   } as TypeLink;
 };
 
diff --git a/packages/printer-legacy/src/printer.ts b/packages/printer-legacy/src/printer.ts
index 42b7bcec..30d8bd73 100644
--- a/packages/printer-legacy/src/printer.ts
+++ b/packages/printer-legacy/src/printer.ts
@@ -126,6 +126,8 @@ export class Printer implements IPrinter {
       typeBadges:
         printTypeOptions?.typeBadges ?? PRINT_TYPE_DEFAULT_OPTIONS.typeBadges,
       metatags: metatags ?? [],
+      useApiGroup:
+        printTypeOptions?.useApiGroup ?? PRINT_TYPE_DEFAULT_OPTIONS.useApiGroup,
     };
   }
 
diff --git a/packages/types/src/core.d.ts b/packages/types/src/core.d.ts
index 919051a3..fef8334c 100644
--- a/packages/types/src/core.d.ts
+++ b/packages/types/src/core.d.ts
@@ -10,6 +10,11 @@ export interface ConfigDocOptions {
   frontMatter: Maybe<FrontMatterOptions>;
 }
 
+export type RendererDocOptions = ConfigDocOptions & {
+  deprecated: TypeDeprecatedOption;
+  useApiGroup: boolean;
+};
+
 export interface DeprecatedConfigDocOptions {
   pagination?: boolean;
   toc?: boolean;
@@ -23,6 +28,7 @@ export interface ConfigPrintTypeOptions {
   parentTypePrefix?: boolean;
   relatedTypeSection?: boolean;
   typeBadges?: boolean;
+  useApiGroup?: boolean;
 }
 
 export type DiffMethodName = string & { _opaque: typeof DIFF_METHOD_NAME };
@@ -65,6 +71,7 @@ export interface CliOptions {
   homepage?: string;
   index?: boolean;
   link?: string;
+  noApiGroup?: boolean;
   noCode?: boolean;
   noParentType?: boolean;
   noRelatedType?: boolean;
diff --git a/packages/types/src/printer.d.ts b/packages/types/src/printer.d.ts
index 83789e63..2b4119f6 100644
--- a/packages/types/src/printer.d.ts
+++ b/packages/types/src/printer.d.ts
@@ -33,6 +33,7 @@ export interface PrinterConfigPrintTypeOptions {
   parentTypePrefix?: boolean;
   relatedTypeSection?: boolean;
   typeBadges?: boolean;
+  useApiGroup?: boolean;
 }
 
 export interface CollapsibleOption {
@@ -57,6 +58,7 @@ export interface PrintTypeOptions {
   schema?: Maybe<GraphQLSchema>;
   skipDocDirectives?: GraphQLDirective[];
   typeBadges?: boolean;
+  useApiGroup?: boolean;
   withAttributes?: boolean;
 }
 
@@ -86,6 +88,7 @@ export type PrintLinkOptions = Partial<PrintTypeOptions> &
     | "parentType"
     | "parentTypePrefix"
     | "skipDocDirectives"
+    | "useApiGroup"
     | "withAttributes"
   >;
 
diff --git a/website/src/css/custom.css b/website/src/css/custom.css
index 91fdf00b..66103544 100644
--- a/website/src/css/custom.css
+++ b/website/src/css/custom.css
@@ -105,3 +105,9 @@ div[class^="announcementBar_"] {
   color: black !important;
   font-weight: 500;
 }
+
+.theme-doc-sidebar-item-category-level-1.graphql-markdown-api-section > div:first-child {
+	text-transform: uppercase full-width;
+	margin: 5px 5px auto auto;
+	background-color: rgb(246, 248, 250);
+}


Test patch:
diff --git a/packages/core/tests/integration/__snapshots__/generator.spec.ts.snap b/packages/core/tests/integration/__snapshots__/generator.spec.ts.snap
index 121f9195..8ad33c91 100644
--- a/packages/core/tests/integration/__snapshots__/generator.spec.ts.snap
+++ b/packages/core/tests/integration/__snapshots__/generator.spec.ts.snap
@@ -5,16 +5,22 @@ exports[`renderer generateDocFromSchema() Markdown document structure from Graph
   "course/_category_.yml": "label: Course
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "course/mutations/_category_.yml": "label: Mutations
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "course/mutations/add-course.mdx": "add-course",
   "course/mutations/drop-course.mdx": "drop-course",
   "course/queries/_category_.yml": "label: Queries
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "course/queries/all-courses.mdx": "all-courses",
   "course/queries/course.mdx": "course",
@@ -24,16 +30,22 @@ link: null
   "grade/_category_.yml": "label: Grade
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "grade/mutations/_category_.yml": "label: Mutations
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "grade/mutations/update-gpa.mdx": "update-gpa",
   "grade/mutations/update-grade.mdx": "update-grade",
   "grade/queries/_category_.yml": "label: Queries
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "grade/queries/gpa.mdx": "gpa",
   "grade/queries/un-weighted-gpa.mdx": "un-weighted-gpa",
@@ -41,10 +53,14 @@ link: null
   "misc/_category_.yml": "label: Misc
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "misc/directives/_category_.yml": "label: Directives
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "misc/directives/deprecated.mdx": "deprecated",
   "misc/directives/doc.mdx": "doc",
@@ -56,16 +72,22 @@ link: null
   "misc/enums/_category_.yml": "label: Enums
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "misc/enums/roles.mdx": "roles",
   "misc/interfaces/_category_.yml": "label: Interfaces
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "misc/interfaces/record.mdx": "record",
   "misc/objects/_category_.yml": "label: Objects
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "misc/objects/course.mdx": "course",
   "misc/objects/department-information.mdx": "department-information",
@@ -74,11 +96,15 @@ link: null
   "misc/queries/_category_.yml": "label: Queries
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "misc/queries/search-role.mdx": "search-role",
   "misc/scalars/_category_.yml": "label: Scalars
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "misc/scalars/boolean.mdx": "boolean",
   "misc/scalars/date.mdx": "date",
@@ -111,6 +137,8 @@ exports[`renderer generateDocFromSchema() generates Markdown document structure
   "directives/_category_.yml": "label: Directives
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "directives/deprecated.mdx": "deprecated",
   "directives/include.mdx": "include",
@@ -119,22 +147,30 @@ link: null
   "enums/_category_.yml": "label: Enums
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "enums/media-type.mdx": "media-type",
   "generated.md": "Dummy homepage for tweet.graphql",
   "inputs/_category_.yml": "label: Inputs
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "inputs/tweet-content.mdx": "tweet-content",
   "interfaces/_category_.yml": "label: Interfaces
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "interfaces/node.mdx": "node",
   "mutations/_category_.yml": "label: Mutations
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "mutations/create-tweet.mdx": "create-tweet",
   "mutations/delete-tweet.mdx": "delete-tweet",
@@ -142,6 +178,8 @@ link: null
   "objects/_category_.yml": "label: Objects
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "objects/media.mdx": "media",
   "objects/meta.mdx": "meta",
@@ -152,6 +190,8 @@ link: null
   "queries/_category_.yml": "label: Queries
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "queries/notifications-meta.mdx": "notifications-meta",
   "queries/tweet.mdx": "tweet",
@@ -162,6 +202,8 @@ link: null
   "scalars/_category_.yml": "label: Scalars
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "scalars/boolean.mdx": "boolean",
   "scalars/date.mdx": "date",
@@ -185,6 +227,8 @@ module.exports = {
   "subscriptions/_category_.yml": "label: Subscriptions
 position: 1
 link: null
+collapsible: true
+collapsed: true
 ",
   "subscriptions/notifications.mdx": "notifications",
 }
diff --git a/packages/core/tests/integration/generator.spec.ts b/packages/core/tests/integration/generator.spec.ts
index 1ce75593..061e73d8 100644
--- a/packages/core/tests/integration/generator.spec.ts
+++ b/packages/core/tests/integration/generator.spec.ts
@@ -66,6 +66,7 @@ describe("renderer", () => {
         printTypeOptions: {
           ...DEFAULT_OPTIONS.printTypeOptions,
           deprecated: DeprecatedOption.DEFAULT,
+          useApiGroup: false,
         },
         onlyDocDirective: [],
         skipDocDirective: [],
@@ -97,6 +98,7 @@ describe("renderer", () => {
         printTypeOptions: {
           ...DEFAULT_OPTIONS.printTypeOptions,
           deprecated: DeprecatedOption.DEFAULT,
+          useApiGroup: false,
         },
         onlyDocDirective: [],
         skipDocDirective: [],
@@ -137,6 +139,7 @@ describe("renderer", () => {
         printTypeOptions: {
           ...DEFAULT_OPTIONS.printTypeOptions,
           deprecated: DeprecatedOption.DEFAULT,
+          useApiGroup: false,
         },
         onlyDocDirective: [],
         skipDocDirective: [],
diff --git a/packages/core/tests/unit/config.test.ts b/packages/core/tests/unit/config.test.ts
index 17383207..c54bb49e 100644
--- a/packages/core/tests/unit/config.test.ts
+++ b/packages/core/tests/unit/config.test.ts
@@ -235,6 +235,7 @@ describe("config", () => {
           parentTypePrefix: false,
           relatedTypeSection: false,
           typeBadges: false,
+          useApiGroup: false,
         },
         skipDocDirective: ["@noDoc" as DirectiveName],
         customDirective: {
diff --git a/packages/core/tests/unit/generator.test.ts b/packages/core/tests/unit/generator.test.ts
index ff509200..77f6cb6b 100644
--- a/packages/core/tests/unit/generator.test.ts
+++ b/packages/core/tests/unit/generator.test.ts
@@ -50,6 +50,7 @@ describe("generator", () => {
         parentTypePrefix: true,
         relatedTypeSection: true,
         typeBadges: true,
+        useApiGroup: true,
       },
       prettify: true,
       printer: "printer module",
@@ -127,6 +128,7 @@ describe("generator", () => {
         {
           ...options.docOptions,
           deprecated: options.printTypeOptions.deprecated,
+          useApiGroup: options.printTypeOptions.useApiGroup,
         },
       );
     });
diff --git a/packages/core/tests/unit/renderer.test.ts b/packages/core/tests/unit/renderer.test.ts
index 6b497bbb..82c13fd5 100644
--- a/packages/core/tests/unit/renderer.test.ts
+++ b/packages/core/tests/unit/renderer.test.ts
@@ -10,6 +10,7 @@ import type {
 jest.mock("node:fs/promises");
 
 jest.mock("node:path", () => {
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
   return {
     __esModule: true,
     ...jest.requireActual("node:path"),
@@ -21,6 +22,7 @@ jest.mock("@graphql-markdown/printer-legacy");
 import { Printer } from "@graphql-markdown/printer-legacy";
 
 jest.mock("@graphql-markdown/utils", () => {
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
   return {
     __esModule: true,
     ...jest.requireActual("@graphql-markdown/utils"),
@@ -34,6 +36,7 @@ jest.mock("@graphql-markdown/utils", () => {
 import * as Utils from "@graphql-markdown/utils";
 
 jest.mock("@graphql-markdown/graphql", () => {
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
   return {
     __esModule: true,
     ...jest.requireActual("@graphql-markdown/graphql"),
@@ -252,7 +255,31 @@ describe("renderer", () => {
 
         expect(spy).toHaveBeenCalledWith(
           filePath,
-          `label: Foobar\nposition: 1\nlink: null\n`,
+          `label: Foobar\nposition: 1\nlink: null\ncollapsible: true\ncollapsed: true\n`,
+        );
+      });
+
+      test("generate _category_.yml file with options override", async () => {
+        expect.assertions(1);
+
+        const category = "foobar";
+        const outputPath = "/output/docs";
+        const filePath = path.join(outputPath, "_category_.yml");
+
+        jest.spyOn(Utils, "fileExists").mockResolvedValue(false);
+        const spy = jest.spyOn(Utils, "saveFile");
+
+        await rendererInstance.generateCategoryMetafile(
+          category,
+          outputPath,
+          undefined,
+          undefined,
+          { collapsed: false, collapsible: false },
+        );
+
+        expect(spy).toHaveBeenCalledWith(
+          filePath,
+          `label: Foobar\nposition: 1\nlink: null\ncollapsible: false\ncollapsed: false\n`,
         );
       });
 
@@ -275,7 +302,7 @@ describe("renderer", () => {
 
         expect(spy).toHaveBeenCalledWith(
           filePath,
-          `label: Foobar\nposition: 1\nlink: \n  type: generated-index\n  title: 'Foobar overview'\n`,
+          `label: Foobar\nposition: 1\nlink: \n  type: generated-index\n  title: 'Foobar overview'\ncollapsible: true\ncollapsed: true\n`,
         );
       });
 
@@ -293,7 +320,7 @@ describe("renderer", () => {
 
         expect(spy).not.toHaveBeenCalledWith(
           filePath,
-          `label: Foobar\nposition: 1\nlink: \n  type: generated-index\n  title: 'Foobar overview'\n`,
+          `label: Foobar\nposition: 1\nlink: \n  type: generated-index\n  title: 'Foobar overview'\ncollapsible: true\ncollapsed: true\n`,
         );
       });
 
@@ -315,7 +342,7 @@ describe("renderer", () => {
 
         expect(spy).toHaveBeenCalledWith(
           filePath,
-          `label: Foobar\nposition: 42\nlink: null\n`,
+          `label: Foobar\nposition: 42\nlink: null\ncollapsible: true\ncollapsed: true\n`,
         );
       });
 
@@ -339,7 +366,7 @@ describe("renderer", () => {
 
         expect(spy).toHaveBeenCalledWith(
           filePath,
-          `label: Foobar\nposition: 42\nclassName: ${styleClass}\nlink: null\n`,
+          `label: Foobar\nposition: 42\nclassName: ${styleClass}\nlink: null\ncollapsible: true\ncollapsed: true\n`,
         );
       });
     });
@@ -390,8 +417,9 @@ describe("renderer", () => {
         jest.replaceProperty(rendererInstance, "options", {
           frontMatter: undefined,
           deprecated: "group",
+          useApiGroup: false,
         });
-        jest.spyOn(GraphQL, "isDeprecated").mockReturnValue(true);
+        jest.spyOn(GraphQL, "isDeprecated").mockReturnValueOnce(true);
 
         const dirPath = await rendererInstance.generateCategoryMetafileType(
           type,
@@ -418,8 +446,9 @@ describe("renderer", () => {
         jest.replaceProperty(rendererInstance, "options", {
           frontMatter: undefined,
           deprecated: "group",
+          useApiGroup: false,
         });
-        jest.spyOn(GraphQL, "isDeprecated").mockReturnValue(false);
+        jest.spyOn(GraphQL, "isDeprecated").mockReturnValueOnce(false);
 
         const dirPath = await rendererInstance.generateCategoryMetafileType(
           type,
@@ -440,8 +469,9 @@ describe("renderer", () => {
         jest.replaceProperty(rendererInstance, "options", {
           frontMatter: undefined,
           deprecated: "default",
+          useApiGroup: false,
         });
-        jest.spyOn(GraphQL, "isDeprecated").mockReturnValue(false);
+        jest.spyOn(GraphQL, "isDeprecated").mockReturnValueOnce(false);
 
         const dirPath = await rendererInstance.generateCategoryMetafileType(
           type,
@@ -462,11 +492,12 @@ describe("renderer", () => {
         jest.replaceProperty(rendererInstance, "options", {
           frontMatter: undefined,
           deprecated: "group",
+          useApiGroup: false,
         });
         jest.replaceProperty(rendererInstance, "group", {
           [root]: { [name]: group },
         });
-        jest.spyOn(GraphQL, "isDeprecated").mockReturnValue(true);
+        jest.spyOn(GraphQL, "isDeprecated").mockReturnValueOnce(true);
 
         const dirPath = await rendererInstance.generateCategoryMetafileType(
           type,
@@ -479,6 +510,50 @@ describe("renderer", () => {
           `/output/deprecated/${group}/${root.toLowerCase()}`,
         );
       });
+
+      test("generate api _category_.yml file", async () => {
+        expect.assertions(2);
+
+        const spy = jest.spyOn(rendererInstance, "generateCategoryMetafile");
+        jest.replaceProperty(rendererInstance, "options", {
+          frontMatter: undefined,
+          deprecated: "default",
+          useApiGroup: true,
+        });
+
+        jest.spyOn(GraphQL, "isApiType").mockReturnValueOnce(true);
+
+        const dirPath = await rendererInstance.generateCategoryMetafileType(
+          {},
+          "Foo",
+          "queries",
+        );
+
+        expect(spy).toHaveBeenCalledWith("queries", "/output/api/queries");
+        expect(dirPath).toBe("/output/api/queries");
+      });
+
+      test("generate system _category_.yml file", async () => {
+        expect.assertions(2);
+
+        const spy = jest.spyOn(rendererInstance, "generateCategoryMetafile");
+        jest.replaceProperty(rendererInstance, "options", {
+          frontMatter: undefined,
+          deprecated: "default",
+          useApiGroup: true,
+        });
+
+        jest.spyOn(GraphQL, "isApiType").mockReturnValueOnce(false);
+
+        const dirPath = await rendererInstance.generateCategoryMetafileType(
+          {},
+          "Foo",
+          "objects",
+        );
+
+        expect(spy).toHaveBeenCalledWith("objects", "/output/types/objects");
+        expect(dirPath).toBe("/output/types/objects");
+      });
     });
   });
 });
diff --git a/packages/docusaurus/tests/__data__/groups.md b/packages/docusaurus/tests/__data__/groups.md
index 9b255374..96acc0c1 100644
--- a/packages/docusaurus/tests/__data__/groups.md
+++ b/packages/docusaurus/tests/__data__/groups.md
@@ -32,10 +32,11 @@ This is an example of documentation grouping with GraphQL directive using the `g
     index: true,
   },
   printTypeOptions: {
+    deprecated: "group",
     parentTypePrefix: false,
     relatedTypeSection: false,
     typeBadges: true,
-    deprecated: "group"
+    useApiGroup: false
   },
   skipDocDirective: ["@noDoc"],
   customDirective: {
@@ -80,10 +81,11 @@ This is an example of documentation grouping with GraphQL directive using the `g
         index: true,
       },
       printTypeOptions: {
+        deprecated: "group",
         parentTypePrefix: false,
         relatedTypeSection: false,
         typeBadges: true,
-        deprecated: "group"
+        useApiGroup: false
       },
       skipDocDirective: ["@noDoc"],
       customDirective: {
@@ -125,6 +127,7 @@ npx docusaurus graphql-to-doc \
     --noParentType \
     --noRelatedType \
     --deprecated group
+    --noApiGroup
 ```
 
 </TabItem>
diff --git a/packages/graphql/tests/unit/directive.test.ts b/packages/graphql/tests/unit/directive.test.ts
index 168884ae..166f5049 100644
--- a/packages/graphql/tests/unit/directive.test.ts
+++ b/packages/graphql/tests/unit/directive.test.ts
@@ -1,4 +1,4 @@
-import { GraphQLDirective, buildSchema } from "graphql";
+import { DirectiveLocation, GraphQLDirective, buildSchema } from "graphql";
 
 import type {
   DirectiveName,
@@ -337,4 +337,64 @@ describe("directive", () => {
       `);
     });
   });
+
+  describe("DirectiveLocation", () => {
+    const locations = [
+      { locations: [DirectiveLocation.QUERY], executable: true },
+      { locations: [DirectiveLocation.MUTATION], executable: true },
+      { locations: [DirectiveLocation.SUBSCRIPTION], executable: true },
+      { locations: [DirectiveLocation.FIELD], executable: true },
+      { locations: [DirectiveLocation.FRAGMENT_DEFINITION], executable: true },
+      { locations: [DirectiveLocation.FRAGMENT_SPREAD], executable: true },
+      { locations: [DirectiveLocation.INLINE_FRAGMENT], executable: true },
+      { locations: [DirectiveLocation.VARIABLE_DEFINITION], executable: true },
+      {
+        locations: [DirectiveLocation.QUERY, DirectiveLocation.ENUM],
+        executable: true,
+      },
+      {
+        locations: [DirectiveLocation.ENUM],
+        executable: false,
+      },
+      {
+        locations: [],
+        executable: false,
+      },
+    ];
+    describe("executableDirectiveLocation()", () => {
+      test.each(locations)(
+        "returns $executable if location is $locations",
+        ({ locations, executable }) => {
+          expect.assertions(1);
+
+          const directive = new GraphQLDirective({
+            name: "testExecutableDirectiveLocation",
+            locations,
+          });
+
+          expect(Directives.executableDirectiveLocation(directive)).toBe(
+            executable,
+          );
+        },
+      );
+    });
+
+    describe("typeSystemDirectiveLocation()", () => {
+      test.each(locations)(
+        "returns !$executable if location is $locations",
+        ({ locations, executable }) => {
+          expect.assertions(1);
+
+          const directive = new GraphQLDirective({
+            name: "testExecutableDirectiveLocation",
+            locations,
+          });
+
+          expect(Directives.typeSystemDirectiveLocation(directive)).not.toBe(
+            executable,
+          );
+        },
+      );
+    });
+  });
 });
diff --git a/packages/graphql/tests/unit/guard.test.ts b/packages/graphql/tests/unit/guard.test.ts
index 6291041e..8227b4f6 100644
--- a/packages/graphql/tests/unit/guard.test.ts
+++ b/packages/graphql/tests/unit/guard.test.ts
@@ -5,7 +5,13 @@ import type { GraphQLSchema } from "graphql";
 import { buildSchema, GraphQLObjectType } from "graphql";
 
 import { getOperation, getTypeFromSchema } from "../../src/introspection";
-import { isDeprecated, isOperation, isGraphQLFieldType } from "../../src/guard";
+import {
+  isDeprecated,
+  isOperation,
+  isGraphQLFieldType,
+  isApiType,
+  isSystemType,
+} from "../../src/guard";
 
 const SCHEMA_FILE = require.resolve("../__data__/tweet.graphql");
 
@@ -123,4 +129,75 @@ describe("graphql", () => {
       expect(isDeprecated({ isDeprecated: true })).toBeTruthy();
     });
   });
+
+  describe("ApiType or SystemType", () => {
+    const schema = buildSchema(`
+    directive @noDoc on OBJECT | INTERFACE | FIELD_DEFINITION
+
+    interface Record {
+      id: String!
+    }
+
+    type StudyItem implements Record {
+      id: String!
+      subject: String!
+      duration: Int!
+    }
+    
+    type Query {
+      getStudyItems(subject: String): [StudyItem!]
+      getStudyItem(id: String!): StudyItem @deprecated
+    }
+  `);
+
+    const types = [
+      {
+        gqlType: schema.getQueryType()!.getFields().getStudyItems,
+        isApi: true,
+        text: "operation",
+      },
+      {
+        gqlType: schema.getDirective("include"),
+        isApi: true,
+        text: "executable directive",
+      },
+      {
+        gqlType: schema.getDirective("noDoc"),
+        isApi: false,
+        text: "system directive",
+      },
+      {
+        gqlType: schema.getType("Record"),
+        isApi: false,
+        text: "interface type",
+      },
+      {
+        gqlType: schema.getType("StudyItem"),
+        isApi: false,
+        text: "object type",
+      },
+    ];
+
+    describe("isApiType()", () => {
+      test.each(types)(
+        "returns $isApiType if type is $text",
+        ({ gqlType, isApi }) => {
+          expect.assertions(1);
+
+          expect(isApiType(gqlType)).toBe(isApi);
+        },
+      );
+    });
+
+    describe("isSystemType()", () => {
+      test.each(types)(
+        "returns !$isApiType if type is $text",
+        ({ gqlType, isApi }) => {
+          expect.assertions(1);
+
+          expect(isSystemType(gqlType)).not.toBe(isApi);
+        },
+      );
+    });
+  });
 });
diff --git a/packages/printer-legacy/tests/unit/__snapshots__/link.test.ts.snap b/packages/printer-legacy/tests/unit/__snapshots__/link.test.ts.snap
index 7fdce54b..7d7c13d9 100644
--- a/packages/printer-legacy/tests/unit/__snapshots__/link.test.ts.snap
+++ b/packages/printer-legacy/tests/unit/__snapshots__/link.test.ts.snap
@@ -1,113 +1,73 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`link getLinkCategory() returns a category object matching the graphLQLNamedType Directive 1`] = `
-{
-  "plural": "directives",
-  "singular": "directive",
-}
-`;
+exports[`link getLinkCategoryFolder() returns a category object matching the graphLQLNamedType Directive 1`] = `"directives"`;
 
-exports[`link getLinkCategory() returns a category object matching the graphLQLNamedType Enum 1`] = `
-{
-  "plural": "enums",
-  "singular": "enum",
-}
-`;
+exports[`link getLinkCategoryFolder() returns a category object matching the graphLQLNamedType Enum 1`] = `"enums"`;
 
-exports[`link getLinkCategory() returns a category object matching the graphLQLNamedType Input 1`] = `
-{
-  "plural": "inputs",
-  "singular": "input",
-}
-`;
+exports[`link getLinkCategoryFolder() returns a category object matching the graphLQLNamedType Input 1`] = `"inputs"`;
 
-exports[`link getLinkCategory() returns a category object matching the graphLQLNamedType Interface 1`] = `
-{
-  "plural": "interfaces",
-  "singular": "interface",
-}
-`;
+exports[`link getLinkCategoryFolder() returns a category object matching the graphLQLNamedType Interface 1`] = `"interfaces"`;
 
-exports[`link getLinkCategory() returns a category object matching the graphLQLNamedType Object 1`] = `
-{
-  "plural": "objects",
-  "singular": "object",
-}
-`;
+exports[`link getLinkCategoryFolder() returns a category object matching the graphLQLNamedType Object 1`] = `"objects"`;
 
-exports[`link getLinkCategory() returns a category object matching the graphLQLNamedType Operation 1`] = `
-{
-  "plural": "operations",
-  "singular": "operation",
-}
-`;
+exports[`link getLinkCategoryFolder() returns a category object matching the graphLQLNamedType Operation 1`] = `undefined`;
 
-exports[`link getLinkCategory() returns a category object matching the graphLQLNamedType Scalar 1`] = `
-{
-  "plural": "scalars",
-  "singular": "scalar",
-}
-`;
+exports[`link getLinkCategoryFolder() returns a category object matching the graphLQLNamedType Scalar 1`] = `"scalars"`;
 
-exports[`link getLinkCategory() returns a category object matching the graphLQLNamedType Union 1`] = `
-{
-  "plural": "unions",
-  "singular": "union",
-}
-`;
+exports[`link getLinkCategoryFolder() returns a category object matching the graphLQLNamedType Union 1`] = `"unions"`;
 
 exports[`link toLink() returns markdown link for GraphQL Directive 1`] = `
 {
   "text": "TestDirective",
-  "url": "docs/graphql/directives/test-directive",
+  "url": "docs/graphql/api/directives/test-directive",
 }
 `;
 
 exports[`link toLink() returns markdown link for GraphQL Enum 1`] = `
 {
   "text": "TestEnum",
-  "url": "docs/graphql/enums/test-enum",
+  "url": "docs/graphql/api/enums/test-enum",
 }
 `;
 
 exports[`link toLink() returns markdown link for GraphQL Input 1`] = `
 {
   "text": "TestInput",
-  "url": "docs/graphql/inputs/test-input",
+  "url": "docs/graphql/api/inputs/test-input",
 }
 `;
 
 exports[`link toLink() returns markdown link for GraphQL Interface 1`] = `
 {
   "text": "TestInterface",
-  "url": "docs/graphql/interfaces/test-interface",
+  "url": "docs/graphql/api/interfaces/test-interface",
 }
 `;
 
 exports[`link toLink() returns markdown link for GraphQL Object 1`] = `
 {
   "text": "TestObject",
-  "url": "docs/graphql/objects/test-object",
+  "url": "docs/graphql/api/objects/test-object",
 }
 `;
 
 exports[`link toLink() returns markdown link for GraphQL Operation 1`] = `
 {
   "text": "TestOperation",
-  "url": "docs/graphql/queries/test-operation",
+  "url": "docs/graphql/api/queries/test-operation",
 }
 `;
 
 exports[`link toLink() returns markdown link for GraphQL Scalar 1`] = `
 {
   "text": "TestScalar",
-  "url": "docs/graphql/scalars/test-scalar",
+  "url": "docs/graphql/api/scalars/test-scalar",
 }
 `;
 
 exports[`link toLink() returns markdown link for GraphQL Union 1`] = `
 {
   "text": "TestUnion",
-  "url": "docs/graphql/unions/test-union",
+  "url": "docs/graphql/api/unions/test-union",
 }
 `;
diff --git a/packages/printer-legacy/tests/unit/badge.test.ts b/packages/printer-legacy/tests/unit/badge.test.ts
index 059b0c6c..67f9f886 100644
--- a/packages/printer-legacy/tests/unit/badge.test.ts
+++ b/packages/printer-legacy/tests/unit/badge.test.ts
@@ -35,7 +35,7 @@ import * as GraphQL from "@graphql-markdown/graphql";
 
 jest.mock("../../src/link", () => {
   return {
-    getLinkCategory: jest.fn(),
+    getCategoryLocale: jest.fn(),
   };
 });
 import * as Link from "../../src/link";
@@ -139,7 +139,7 @@ describe("badge", () => {
     test("return category name as badge is type is subtype", () => {
       expect.assertions(1);
 
-      jest.spyOn(Link, "getLinkCategory").mockReturnValueOnce("foobar");
+      jest.spyOn(Link, "getCategoryLocale").mockReturnValueOnce("foobar");
 
       const type = {};
 
diff --git a/packages/printer-legacy/tests/unit/code.test.ts b/packages/printer-legacy/tests/unit/code.test.ts
index f03a2fe9..a897e79a 100644
--- a/packages/printer-legacy/tests/unit/code.test.ts
+++ b/packages/printer-legacy/tests/unit/code.test.ts
@@ -8,13 +8,16 @@ import {
 jest.mock("@graphql-markdown/graphql", () => {
   return {
     getFormattedDefaultValue: jest.fn((type) => {
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-return
       return type?.defaultValue;
     }),
-    getTypeName: jest.fn((t) => {
+    getTypeName: jest.fn((t): string => {
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-return
       return t.name ?? t.toString();
     }),
     hasDirective: jest.fn(),
     isDeprecated: jest.fn((t) => {
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-return
       return t.isDeprecated || false;
     }),
   };
diff --git a/packages/printer-legacy/tests/unit/graphql/directive.test.ts b/packages/printer-legacy/tests/unit/graphql/directive.test.ts
index 0640e14e..11e9f985 100644
--- a/packages/printer-legacy/tests/unit/graphql/directive.test.ts
+++ b/packages/printer-legacy/tests/unit/graphql/directive.test.ts
@@ -40,7 +40,7 @@ describe("directive", () => {
       expect(code).toMatchInlineSnapshot(`
         "### Arguments
 
-        #### [<code style={{ fontWeight: 'normal' }}>FooBar.<b>ArgFooBar</b></code>](#)<Bullet />[\`Boolean\`](/scalars/boolean) <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>FooBar.<b>ArgFooBar</b></code>](#)<Bullet />[\`Boolean\`](/types/scalars/boolean) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
@@ -75,7 +75,7 @@ describe("directive", () => {
       expect(code).toMatchInlineSnapshot(`
         "### Arguments
 
-        #### [<code style={{ fontWeight: 'normal' }}>FooBar.<b>Foo</b></code>](#)<Bullet />[\`Boolean\`](/scalars/boolean) <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>FooBar.<b>Foo</b></code>](#)<Bullet />[\`Boolean\`](/types/scalars/boolean) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
@@ -85,7 +85,7 @@ describe("directive", () => {
 
         <Details dataOpen={<><span className="deprecated">Hide deprecated</span></>} dataClose={<><span className="deprecated">Show deprecated</span></>}>
 
-        #### [<code style={{ fontWeight: 'normal' }}>FooBar.<b>Bar</b></code>](#)<Bullet />[\`Boolean\`](/scalars/boolean) <Badge class="badge badge--deprecated badge--secondary" text="deprecated"/> <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>FooBar.<b>Bar</b></code>](#)<Bullet />[\`Boolean\`](/types/scalars/boolean) <Badge class="badge badge--deprecated badge--secondary" text="deprecated"/> <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > :::warning[DEPRECATED]
diff --git a/packages/printer-legacy/tests/unit/graphql/input.test.ts b/packages/printer-legacy/tests/unit/graphql/input.test.ts
index 6984bf16..f743989e 100644
--- a/packages/printer-legacy/tests/unit/graphql/input.test.ts
+++ b/packages/printer-legacy/tests/unit/graphql/input.test.ts
@@ -22,13 +22,13 @@ describe("input", () => {
       expect(metadata).toMatchInlineSnapshot(`
         "### Fields
 
-        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>one</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>one</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
         > 
 
-        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>two</b></code>](#)<Bullet />[\`Boolean\`](/scalars/boolean) <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>two</b></code>](#)<Bullet />[\`Boolean\`](/types/scalars/boolean) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
diff --git a/packages/printer-legacy/tests/unit/graphql/interface.test.ts b/packages/printer-legacy/tests/unit/graphql/interface.test.ts
index b06e42ae..34a120ec 100644
--- a/packages/printer-legacy/tests/unit/graphql/interface.test.ts
+++ b/packages/printer-legacy/tests/unit/graphql/interface.test.ts
@@ -33,23 +33,23 @@ describe("interface", () => {
       expect(metadata).toMatchInlineSnapshot(`
         "### Fields
 
-        #### [<code style={{ fontWeight: 'normal' }}>TestInterfaceName.<b>one</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>TestInterfaceName.<b>one</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
         > 
 
-        #### [<code style={{ fontWeight: 'normal' }}>TestInterfaceName.<b>two</b></code>](#)<Bullet />[\`Boolean\`](/scalars/boolean) <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>TestInterfaceName.<b>two</b></code>](#)<Bullet />[\`Boolean\`](/types/scalars/boolean) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
         > 
 
-        #### [<code style={{ fontWeight: 'normal' }}>TestInterfaceName.<b>three</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>TestInterfaceName.<b>three</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
-        > ##### [<code style={{ fontWeight: 'normal' }}>TestInterfaceName.three.<b>four</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
+        > ##### [<code style={{ fontWeight: 'normal' }}>TestInterfaceName.three.<b>four</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
diff --git a/packages/printer-legacy/tests/unit/graphql/object.test.ts b/packages/printer-legacy/tests/unit/graphql/object.test.ts
index 5e04a927..7b7d6c2a 100644
--- a/packages/printer-legacy/tests/unit/graphql/object.test.ts
+++ b/packages/printer-legacy/tests/unit/graphql/object.test.ts
@@ -43,13 +43,13 @@ describe("object", () => {
       expect(metadata).toMatchInlineSnapshot(`
         "### Fields
 
-        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>one</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>one</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
         > 
 
-        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>two</b></code>](#)<Bullet />[\`Boolean\`](/scalars/boolean) <Badge class="badge badge--deprecated badge--secondary" text="deprecated"/> <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>two</b></code>](#)<Bullet />[\`Boolean\`](/types/scalars/boolean) <Badge class="badge badge--deprecated badge--secondary" text="deprecated"/> <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > :::warning[DEPRECATED]
@@ -61,11 +61,11 @@ describe("object", () => {
         > 
         > 
 
-        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>three</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>three</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
-        > ##### [<code style={{ fontWeight: 'normal' }}>TestName.three.<b>four</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
+        > ##### [<code style={{ fontWeight: 'normal' }}>TestName.three.<b>four</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
@@ -73,7 +73,7 @@ describe("object", () => {
 
         ### Interfaces
 
-        #### [\`TestInterfaceName\`](/interfaces/test-interface-name) <Badge class="badge badge--secondary" text="interface"/> 
+        #### [\`TestInterfaceName\`](/types/interfaces/test-interface-name) <Badge class="badge badge--secondary" text="interface"/> 
         > 
         > 
         > 
@@ -94,17 +94,17 @@ describe("object", () => {
       expect(metadata).toMatchInlineSnapshot(`
         "### Fields
 
-        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>one</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>one</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
         > 
 
-        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>three</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>three</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
-        > ##### [<code style={{ fontWeight: 'normal' }}>TestName.three.<b>four</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
+        > ##### [<code style={{ fontWeight: 'normal' }}>TestName.three.<b>four</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
@@ -114,7 +114,7 @@ describe("object", () => {
 
         <Details dataOpen={<><span className="deprecated">Hide deprecated</span></>} dataClose={<><span className="deprecated">Show deprecated</span></>}>
 
-        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>two</b></code>](#)<Bullet />[\`Boolean\`](/scalars/boolean) <Badge class="badge badge--deprecated badge--secondary" text="deprecated"/> <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>TestName.<b>two</b></code>](#)<Bullet />[\`Boolean\`](/types/scalars/boolean) <Badge class="badge badge--deprecated badge--secondary" text="deprecated"/> <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > :::warning[DEPRECATED]
@@ -130,7 +130,7 @@ describe("object", () => {
 
         ### Interfaces
 
-        #### [\`TestInterfaceName\`](/interfaces/test-interface-name) <Badge class="badge badge--secondary" text="interface"/> 
+        #### [\`TestInterfaceName\`](/types/interfaces/test-interface-name) <Badge class="badge badge--secondary" text="interface"/> 
         > 
         > 
         > 
diff --git a/packages/printer-legacy/tests/unit/graphql/operation.test.ts b/packages/printer-legacy/tests/unit/graphql/operation.test.ts
index 2f4e6a46..0fcb0c4e 100644
--- a/packages/printer-legacy/tests/unit/graphql/operation.test.ts
+++ b/packages/printer-legacy/tests/unit/graphql/operation.test.ts
@@ -4,6 +4,7 @@ import { GraphQLID, GraphQLObjectType, GraphQLString } from "graphql";
 jest.mock("@graphql-markdown/graphql", () => {
   return {
     ...jest.requireActual("@graphql-markdown/graphql"),
+    isDirectiveType: jest.fn(),
     isDeprecated: jest.fn((T: any) => {
       return "deprecationReason" in T;
     }), // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -43,7 +44,7 @@ describe("operation", () => {
       expect(metadata).toMatchInlineSnapshot(`
         "### Type
 
-        #### [\`Test\`](/objects/test) <Badge class="badge badge--secondary" text="object"/> 
+        #### [\`Test\`](/types/objects/test) <Badge class="badge badge--secondary" text="object"/> 
         > 
         > 
         > 
@@ -82,7 +83,7 @@ describe("operation", () => {
       expect(metadata).toMatchInlineSnapshot(`
         "### Arguments
 
-        #### [<code style={{ fontWeight: 'normal' }}>TestQuery.<b>ArgFooBar</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>TestQuery.<b>ArgFooBar</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
@@ -90,7 +91,7 @@ describe("operation", () => {
 
         ### Type
 
-        #### [\`Test\`](/objects/test) <Badge class="badge badge--secondary" text="object"/> 
+        #### [\`Test\`](/types/objects/test) <Badge class="badge badge--secondary" text="object"/> 
         > 
         > 
         > 
@@ -135,7 +136,7 @@ describe("operation", () => {
       expect(metadata).toMatchInlineSnapshot(`
         "### Arguments
 
-        #### [<code style={{ fontWeight: 'normal' }}>TestQuery.<b>Foo</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>TestQuery.<b>Foo</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
@@ -145,7 +146,7 @@ describe("operation", () => {
 
         <Details dataOpen={<><span className="deprecated">Hide deprecated</span></>} dataClose={<><span className="deprecated">Show deprecated</span></>}>
 
-        #### [<code style={{ fontWeight: 'normal' }}>TestQuery.<b>Bar</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--deprecated badge--secondary" text="deprecated"/> <Badge class="badge badge--secondary" text="scalar"/> 
+        #### [<code style={{ fontWeight: 'normal' }}>TestQuery.<b>Bar</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--deprecated badge--secondary" text="deprecated"/> <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > :::warning[DEPRECATED]
@@ -161,7 +162,7 @@ describe("operation", () => {
 
         ### Type
 
-        #### [\`Test\`](/objects/test) <Badge class="badge badge--secondary" text="object"/> 
+        #### [\`Test\`](/types/objects/test) <Badge class="badge badge--secondary" text="object"/> 
         > 
         > 
         > 
diff --git a/packages/printer-legacy/tests/unit/graphql/union.test.ts b/packages/printer-legacy/tests/unit/graphql/union.test.ts
index e62ee479..5bcf98ab 100644
--- a/packages/printer-legacy/tests/unit/graphql/union.test.ts
+++ b/packages/printer-legacy/tests/unit/graphql/union.test.ts
@@ -25,13 +25,13 @@ describe("union", () => {
 
       expect(code).toBe(`### Possible types
 
-#### [<code style={{ fontWeight: 'normal' }}>UnionTypeName.<b>one</b></code>](/objects/one) <Badge class="badge badge--secondary" text="object"/> 
+#### [<code style={{ fontWeight: 'normal' }}>UnionTypeName.<b>one</b></code>](/types/objects/one) <Badge class="badge badge--secondary" text="object"/> 
 > 
 > 
 > 
 > 
 
-#### [<code style={{ fontWeight: 'normal' }}>UnionTypeName.<b>two</b></code>](/objects/two) <Badge class="badge badge--secondary" text="object"/> 
+#### [<code style={{ fontWeight: 'normal' }}>UnionTypeName.<b>two</b></code>](/types/objects/two) <Badge class="badge badge--secondary" text="object"/> 
 > 
 > 
 > 
diff --git a/packages/printer-legacy/tests/unit/link.test.ts b/packages/printer-legacy/tests/unit/link.test.ts
index 8f0b3112..010bbf7f 100644
--- a/packages/printer-legacy/tests/unit/link.test.ts
+++ b/packages/printer-legacy/tests/unit/link.test.ts
@@ -20,21 +20,24 @@ const mockUtils = jest.mocked(Utils);
 import * as GraphQL from "@graphql-markdown/graphql";
 jest.mock("@graphql-markdown/graphql", () => {
   return {
-    hasDirective: jest.fn(),
-    getTypeName: jest.fn(),
+    executableDirectiveLocation: jest.fn(),
     getNamedType: jest.fn(),
-    isOperation: jest.fn(),
+    getTypeName: jest.fn(),
+    hasDirective: jest.fn(),
+    isApiType: jest.fn(),
+    isDeprecated: jest.fn(),
+    isDirective: jest.fn(),
+    isDirectiveType: jest.fn(),
     isEnumType: jest.fn(),
-    isUnionType: jest.fn(),
-    isInterfaceType: jest.fn(),
-    isObjectType: jest.fn(),
     isInputType: jest.fn(),
-    isScalarType: jest.fn(),
-    isDirectiveType: jest.fn(),
+    isInterfaceType: jest.fn(),
     isLeafType: jest.fn(),
     isListType: jest.fn(),
     isNonNullType: jest.fn(),
-    isDeprecated: jest.fn(),
+    isObjectType: jest.fn(),
+    isOperation: jest.fn(),
+    isScalarType: jest.fn(),
+    isUnionType: jest.fn(),
   };
 });
 const mockGraphQL = jest.mocked(GraphQL);
@@ -109,7 +112,7 @@ describe("link", () => {
     jest.resetAllMocks();
   });
 
-  describe("getLinkCategory()", () => {
+  describe("getLinkCategoryFolder()", () => {
     test.each(types)(
       "returns a category object matching the graphLQLNamedType $name",
       ({ guard }) => {
@@ -119,7 +122,7 @@ describe("link", () => {
 
         mockGraphQL[guard].mockReturnValueOnce(true);
 
-        const category = Link.getLinkCategory(
+        const category = Link.getLinkCategoryFolder(
           {} as unknown as GraphQLNamedType,
         );
 
@@ -130,7 +133,9 @@ describe("link", () => {
     test("returns undefined if unknown", () => {
       expect.assertions(1);
 
-      const category = Link.getLinkCategory({} as unknown as GraphQLNamedType);
+      const category = Link.getLinkCategoryFolder(
+        {} as unknown as GraphQLNamedType,
+      );
 
       expect(category).toBeUndefined();
     });
@@ -157,7 +162,8 @@ describe("link", () => {
         mockGraphQL.getNamedType.mockReturnValue(
           entityName as unknown as GraphQLNamedType,
         );
-        mockGraphQL[guard].mockReturnValue(true);
+        mockGraphQL[guard].mockReturnValueOnce(true);
+        mockGraphQL.isApiType.mockReturnValue(true);
         mockUtils.slugify.mockReturnValueOnce(slug);
 
         const link = Link.toLink(type, entityName, operation, {
@@ -181,10 +187,11 @@ describe("link", () => {
         }),
       );
 
-      mockGraphQL.getNamedType.mockReturnValue(
+      mockGraphQL.getNamedType.mockReturnValueOnce(
         entityName as unknown as GraphQLNamedType,
       );
-      mockGraphQL.isObjectType.mockReturnValue(true);
+      mockGraphQL.isObjectType.mockReturnValueOnce(true);
+      mockGraphQL.isApiType.mockReturnValueOnce(false);
       mockUtils.slugify.mockReturnValueOnce(slug);
 
       const link = Link.toLink(type, entityName, undefined, {
@@ -195,7 +202,7 @@ describe("link", () => {
       expect(link).toMatchInlineSnapshot(`
         {
           "text": "TestObjectList",
-          "url": "docs/graphql/objects/test-object-list",
+          "url": "docs/graphql/types/objects/test-object-list",
         }
       `);
     });
@@ -260,7 +267,8 @@ describe("link", () => {
       mockGraphQL.getNamedType.mockReturnValue(
         entityName as unknown as GraphQLNamedType,
       );
-      mockGraphQL.isDirectiveType.mockReturnValue(true);
+      mockGraphQL.isDirectiveType.mockReturnValueOnce(true);
+      mockGraphQL.isApiType.mockReturnValueOnce(true);
       mockUtils.slugify.mockReturnValueOnce(slug);
       mockGroup.getGroup.mockReturnValueOnce("group");
 
@@ -273,7 +281,7 @@ describe("link", () => {
       expect(link).toMatchInlineSnapshot(`
         {
           "text": "TestDirective",
-          "url": "docs/graphql/group/directives/test-directive",
+          "url": "docs/graphql/api/group/directives/test-directive",
         }
       `);
     });
@@ -291,7 +299,8 @@ describe("link", () => {
       mockGraphQL.getNamedType.mockReturnValue(
         entityName as unknown as GraphQLNamedType,
       );
-      mockGraphQL.isDirectiveType.mockReturnValue(true);
+      mockGraphQL.isDirectiveType.mockReturnValueOnce(true);
+      mockGraphQL.isApiType.mockReturnValueOnce(true);
       mockUtils.slugify.mockReturnValueOnce(slug);
       mockGraphQL.isDeprecated.mockReturnValue(true);
 
@@ -304,7 +313,37 @@ describe("link", () => {
       expect(link).toMatchInlineSnapshot(`
         {
           "text": "TestDirective",
-          "url": "docs/graphql/deprecated/directives/test-directive",
+          "url": "docs/graphql/deprecated/api/directives/test-directive",
+        }
+      `);
+    });
+
+    test("returns markdown link without api group when disabled", () => {
+      expect.hasAssertions();
+
+      const entityName = `TestDirective`;
+      const slug = `test-directive`;
+      const type = new GraphQLDirective({
+        name: entityName,
+        locations: [],
+      });
+
+      mockGraphQL.getNamedType.mockReturnValue(
+        entityName as unknown as GraphQLNamedType,
+      );
+      mockGraphQL.isDirectiveType.mockReturnValueOnce(true);
+      mockUtils.slugify.mockReturnValueOnce(slug);
+
+      const link = Link.toLink(type, entityName, undefined, {
+        ...DEFAULT_OPTIONS,
+        useApiGroup: false,
+        basePath,
+      });
+
+      expect(link).toMatchInlineSnapshot(`
+        {
+          "text": "TestDirective",
+          "url": "docs/graphql/directives/test-directive",
         }
       `);
     });
@@ -539,7 +578,8 @@ describe("link", () => {
         entityName as unknown as GraphQLNamedType,
       );
       mockGraphQL.isScalarType.mockReturnValue(true);
-      mockUtils.slugify.mockReturnValue(slug);
+      mockGraphQL.isApiType.mockReturnValueOnce(false);
+      mockUtils.slugify.mockReturnValueOnce(slug);
 
       const link = Link.getRelationLink("foo", type, {
         ...DEFAULT_OPTIONS,
@@ -548,7 +588,7 @@ describe("link", () => {
 
       expect(link).toStrictEqual({
         text: "TestScalar",
-        url: "docs/graphql/scalars/test-scalar",
+        url: "docs/graphql/types/scalars/test-scalar",
       });
     });
 
@@ -561,11 +601,11 @@ describe("link", () => {
         name: entityName,
       });
 
-      mockGraphQL.getNamedType.mockReturnValue(
+      mockGraphQL.getNamedType.mockReturnValueOnce(
         entityName as unknown as GraphQLNamedType,
       );
-      mockGraphQL.isScalarType.mockReturnValue(true);
-      mockUtils.slugify.mockReturnValue(slug);
+      mockGraphQL.isScalarType.mockReturnValueOnce(true);
+      mockUtils.slugify.mockReturnValueOnce(slug);
 
       const link = Link.getRelationLink(undefined, type, {
         ...DEFAULT_OPTIONS,
diff --git a/packages/printer-legacy/tests/unit/printer.test.ts b/packages/printer-legacy/tests/unit/printer.test.ts
index ec3ca3ba..60e9648b 100644
--- a/packages/printer-legacy/tests/unit/printer.test.ts
+++ b/packages/printer-legacy/tests/unit/printer.test.ts
@@ -181,6 +181,7 @@ describe("Printer", () => {
           "schema": undefined,
           "skipDocDirectives": [],
           "typeBadges": true,
+          "useApiGroup": true,
           "withAttributes": false,
         }
       `);
@@ -213,6 +214,7 @@ describe("Printer", () => {
           parentTypePrefix: false,
           relatedTypeSection: false,
           typeBadges: false,
+          useApiGroup: false,
         },
         skipDocDirectives: [testDirective],
       });
@@ -266,6 +268,7 @@ describe("Printer", () => {
             "@test",
           ],
           "typeBadges": false,
+          "useApiGroup": false,
           "withAttributes": false,
         }
       `);
diff --git a/packages/printer-legacy/tests/unit/relation.test.ts b/packages/printer-legacy/tests/unit/relation.test.ts
index 035262fa..798844c6 100644
--- a/packages/printer-legacy/tests/unit/relation.test.ts
+++ b/packages/printer-legacy/tests/unit/relation.test.ts
@@ -10,6 +10,7 @@ import type {
 jest.mock("@graphql-markdown/utils", () => {
   return {
     escapeMDX: jest.fn((s) => {
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-return
       return s;
     }),
     pathUrl: jest.fn(),
@@ -20,6 +21,7 @@ jest.mock("@graphql-markdown/utils", () => {
 jest.mock("@graphql-markdown/graphql", () => {
   return {
     getNamedType: jest.fn((t) => {
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-return
       return t;
     }),
     getRelationOfReturn: jest.fn(),
diff --git a/packages/printer-legacy/tests/unit/section.test.ts b/packages/printer-legacy/tests/unit/section.test.ts
index 6cd6c30e..efc416c3 100644
--- a/packages/printer-legacy/tests/unit/section.test.ts
+++ b/packages/printer-legacy/tests/unit/section.test.ts
@@ -169,7 +169,7 @@ describe("section", () => {
       const section = printSectionItem(type, DEFAULT_OPTIONS);
 
       expect(section).toMatchInlineSnapshot(`
-        "#### [\`EntityTypeName\`](/objects/entity-type-name) <Badge class="badge badge--secondary" text="object"/> 
+        "#### [\`EntityTypeName\`](/types/objects/entity-type-name) <Badge class="badge badge--secondary" text="object"/> 
         > 
         > 
         > Lorem ipsum
@@ -198,7 +198,7 @@ sunt in culpa qui officia deserunt mollit anim id est laborum.`,
       const section = printSectionItem(type, DEFAULT_OPTIONS);
 
       expect(section).toMatchInlineSnapshot(`
-        "#### [\`EntityTypeName\`](/objects/entity-type-name) <Badge class="badge badge--secondary" text="object"/> 
+        "#### [\`EntityTypeName\`](/types/objects/entity-type-name) <Badge class="badge badge--secondary" text="object"/> 
         > 
         > 
         > Lorem ipsum dolor sit amet, 
@@ -231,7 +231,7 @@ sunt in culpa qui officia deserunt mollit anim id est laborum.`,
       const section = printSectionItem(type, DEFAULT_OPTIONS);
 
       expect(section).toMatchInlineSnapshot(`
-        "#### [\`EntityTypeName\`](#)<Bullet />[\`NonNullableObjectType!\`](/objects/non-nullable-object-type) <Badge class="badge badge--secondary" text="non-null"/> <Badge class="badge badge--secondary" text="object"/> 
+        "#### [\`EntityTypeName\`](#)<Bullet />[\`NonNullableObjectType!\`](/types/objects/non-nullable-object-type) <Badge class="badge badge--secondary" text="non-null"/> <Badge class="badge badge--secondary" text="object"/> 
         > 
         > 
         > 
@@ -257,7 +257,7 @@ sunt in culpa qui officia deserunt mollit anim id est laborum.`,
       const section = printSectionItem(type, DEFAULT_OPTIONS);
 
       expect(section).toMatchInlineSnapshot(`
-        "#### [\`EntityTypeName\`](#)<Bullet />[\`[NonNullableObjectType]!\`](/objects/non-nullable-object-type) <Badge class="badge badge--secondary" text="non-null"/> <Badge class="badge badge--secondary" text="object"/> 
+        "#### [\`EntityTypeName\`](#)<Bullet />[\`[NonNullableObjectType]!\`](/types/objects/non-nullable-object-type) <Badge class="badge badge--secondary" text="non-null"/> <Badge class="badge badge--secondary" text="object"/> 
         > 
         > 
         > 
@@ -305,7 +305,7 @@ sunt in culpa qui officia deserunt mollit anim id est laborum.`,
         > 
         > 
         > 
-        > ##### [<code style={{ fontWeight: 'normal' }}>EntityTypeName.<b>ParameterTypeName</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
+        > ##### [<code style={{ fontWeight: 'normal' }}>EntityTypeName.<b>ParameterTypeName</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
@@ -324,7 +324,7 @@ sunt in culpa qui officia deserunt mollit anim id est laborum.`,
       const section = printSectionItem(type, DEFAULT_OPTIONS);
 
       expect(section).toMatchInlineSnapshot(`
-        "#### [\`EntityTypeNameList\`](#)<Bullet />[\`[Int!]\`](/scalars/int) <Badge class="badge badge--secondary" text="list"/> <Badge class="badge badge--secondary" text="scalar"/> 
+        "#### [\`EntityTypeNameList\`](#)<Bullet />[\`[Int!]\`](/types/scalars/int) <Badge class="badge badge--secondary" text="list"/> <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
@@ -345,7 +345,7 @@ sunt in culpa qui officia deserunt mollit anim id est laborum.`,
       const section = printSectionItem(type, DEFAULT_OPTIONS);
 
       expect(section).toMatchInlineSnapshot(`
-        "#### [\`EntityTypeNameList\`](#)<Bullet />[\`[Int!]!\`](/scalars/int) <Badge class="badge badge--secondary" text="non-null"/> <Badge class="badge badge--secondary" text="scalar"/> 
+        "#### [\`EntityTypeNameList\`](#)<Bullet />[\`[Int!]!\`](/types/scalars/int) <Badge class="badge badge--secondary" text="non-null"/> <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         > 
@@ -435,7 +435,7 @@ sunt in culpa qui officia deserunt mollit anim id est laborum.`,
         > :::
         > 
         > 
-        > ##### [<code style={{ fontWeight: 'normal' }}>EntityTypeName.<b>ParameterTypeName</b></code>](#)<Bullet />[\`String\`](/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
+        > ##### [<code style={{ fontWeight: 'normal' }}>EntityTypeName.<b>ParameterTypeName</b></code>](#)<Bullet />[\`String\`](/types/scalars/string) <Badge class="badge badge--secondary" text="scalar"/> 
         > 
         > 
         >