How Mermaid's detectType.ts Diagram Detection System Works and How to Register Custom Detectors

Mermaid determines which diagram renderer to use by executing a registry of detector functions defined in detectType.ts, returning the first matching diagram key, and you can extend this pipeline by registering custom detectors via addDetector() or registerLazyLoadedDiagrams() before initialization.

The mermaid-js/mermaid library implements a modular architecture that delegates diagram identification to a pluggable diagram detection system. Central to this mechanism is packages/mermaid/src/diagram-api/detectType.ts, which cleans input text, iterates over registered matchers, and lazy-loads implementations on demand. By leveraging the public detector registry APIs, you can teach Mermaid to recognize entirely new syntaxes without modifying core library code.

How Mermaid Detects Diagram Types

The detection flow follows a strict precedence-based pipeline. When detectType() is invoked, it first sanitizes the input to remove metadata that could interfere with pattern matching.

Pre-processing occurs at lines 36-40 of detectType.ts, where the system strips front-matter, %%{initialize…} directives, and comments:

text = text
  .replace(frontMatterRegex, '')
  .replace(directiveRegex, '')
  .replace(anyCommentRegex, '\n');

Detector iteration happens next (lines 41-46). The function walks the global detectors object and executes each detector(text, config) function until one returns a truthy value:

for (const [key, { detector }] of Object.entries(detectors)) {
  if (detector(text, config)) return key;
}

The key (e.g., "flowchart-elk", "pie") is immediately returned as the diagram type. If no detector matches, the system throws an UnknownDiagramError (lines 48-50). Later, when the diagram renders, getDiagramLoader(key) retrieves the optional loader function to import the implementation on demand (lines 80-82).

The Detector Registry Architecture

The registry is a plain object exported from detectType.ts:

export const detectors: Record<string, DetectorRecord> = {};

Built-in diagrams populate this registry during initialization via packages/mermaid/src/diagram-api/diagram-orchestration.ts. This file imports individual detectors and calls registerLazyLoadedDiagrams() with specific ordering to ensure more specific matchers execute before generic ones:

import flowchartElk from '../diagrams/flowchart/elk/detector.js';
// ...
registerLazyLoadedDiagrams(flowchartElk, mindmap, architecture);

Registering Custom Diagram Detectors

You can inject custom logic into the registry using two primary APIs, depending on whether your diagram implementation requires lazy loading.

Simple Registration with addDetector

For lightweight diagrams already bundled in your application, use addDetector() to register a synchronous detector and optional loader:

import { addDetector } from '@mermaid-js/mermaid/dist/diagram-api/detectType.js';
import type { DiagramDetector, DiagramLoader } from '@mermaid-js/mermaid/dist/diagram-api/types.js';

// Detector function returns true when text matches your syntax
const myDetector: DiagramDetector = (txt) => /^\s*mygraph/.test(txt);

// Optional: Lazy loader for the diagram implementation
const myLoader: DiagramLoader = async () => {
  const { diagram } = await import('./myDiagram.js');
  return { id: 'my-diagram', diagram };
};

// Register to the global detectors map (line 72)
addDetector('my-diagram', myDetector, myLoader);

Lazy-Loaded Registration with registerLazyLoadedDiagrams

For heavy diagram implementations, bundle the definition using ExternalDiagramDefinition and register via registerLazyLoadedDiagrams() (lines 66-70):

import { registerLazyLoadedDiagrams } from '@mermaid-js/mermaid/dist/diagram-api/detectType.js';
import type { ExternalDiagramDefinition } from '@mermaid-js/mermaid/dist/diagram-api/types.js';

const myDiagram: ExternalDiagramDefinition = {
  id: 'my-diagram',
  detector: (txt) => /^\s*mygraph/.test(txt),
  loader: async () => {
    const { diagram } = await import('./myDiagram.js');
    return { id: 'my-diagram', diagram };
  },
};

// Order matters: register specific detectors before generic ones
registerLazyLoadedDiagrams(myDiagram);

Registration Timing Requirements

Registration must complete before Mermaid parses any diagram text. Invoke these helpers early in your application lifecycle, such as immediately after importing the Mermaid library but before calling mermaid.initialize() or mermaid.run(). If registering within a plugin architecture, ensure your module executes prior to the core addDiagrams() function in diagram-orchestration.ts.

Complete Working Example: Custom "Gantt-Lite" Detector

The following implementation demonstrates a custom diagram that triggers on the gantt-lite keyword and lazy-loads its renderer:

// myGanttLiteDetector.ts
import type { ExternalDiagramDefinition } from '@mermaid-js/mermaid/dist/diagram-api/types.js';
import { registerLazyLoadedDiagrams } from '@mermaid-js/mermaid/dist/diagram-api/detectType.js';

// 1. Define the detector logic
const detector = (txt: string) => /^\s*gantt-lite/.test(txt);

// 2. Define the async loader
const loader = async () => {
  const { diagram } = await import('./ganttLiteDiagram.js');
  return { id: 'gantt-lite', diagram };
};

// 3. Bundle as ExternalDiagramDefinition
const ganttLite: ExternalDiagramDefinition = { 
  id: 'gantt-lite', 
  detector, 
  loader 
};

// 4. Register before Mermaid initializes
registerLazyLoadedDiagrams(ganttLite);

Once registered, any Mermaid code block beginning with gantt-lite will trigger your detector, execute the loader to fetch ganttLiteDiagram.js, and render using your custom logic.

Core Source Files and Type Definitions

Understanding these files is essential for advanced customization:

Summary

  • Detection Flow: detectType() sanitizes input, iterates the detectors registry in insertion order, and returns the first matching key or throws UnknownDiagramError.
  • Registry: A global Record<string, DetectorRecord> stored in detectType.ts, populated by diagram-orchestration.ts for built-ins.
  • Registration API: Use addDetector(key, detector, loader?) for eager registration, or registerLazyLoadedDiagrams(...defs) for bundled, ordered registration with lazy loading.
  • Extensibility: Implement DiagramDetector to recognize syntax and DiagramLoader to import implementations, registering them early in the application lifecycle.

Frequently Asked Questions

What happens if multiple detectors match the input text?

The first registered detector returning a truthy value wins. Because detectType.ts iterates with for...of over Object.entries(detectors) (line 41), registration order is critical. Register more specific detectors before generic ones to ensure correct routing.

Can custom detectors modify the Mermaid configuration?

Yes. The DiagramDetector function signature includes a config parameter ((text: string, config?: MermaidConfig) => boolean), allowing detectors to mutate configuration values before rendering. For example, the built-in flowchart-elk detector in packages/mermaid/src/diagrams/flowchart/elk/detector.ts sets config.layout = 'elk' when it detects ELK-specific syntax.

What is the difference between addDetector and registerLazyLoadedDiagrams?

addDetector() registers a single detector and optional loader directly to the global map (line 72), suitable for simple extensions. registerLazyLoadedDiagrams() accepts multiple ExternalDiagramDefinition objects, iterates them in order, and internally calls addDetector() for each (lines 66-70), providing a cleaner API for registering multiple lazy-loaded diagrams with guaranteed precedence.

Where should I place custom detector registration code?

Execute registration code immediately after importing Mermaid but before calling mermaid.init() or mermaid.run(). For browser environments, place your registration script after the Mermaid bundle but before any render calls. For Node.js or bundled applications, ensure your detector module runs before the main addDiagrams() function executes in diagram-orchestration.ts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →