From Diagram Text to SVG: The Rendering Pipeline
Mermaid’s defining operation is not “draw this shape”; it is “interpret this definition and produce a usable visual artifact.” This chapter follows Mermaid JS from incoming text to returned SVG, focusing on the boundaries between validation, parsing, model state, layout, rendering, sanitization, and host integration. By the end, you should be able to tell which part of the pipeline is responsible for a success, a failure, or a decision that still belongs to the application using Mermaid.
The distinction matters because Mermaid offers several public entry points that sound similar. parse() checks a definition without drawing it. render() produces SVG for an application to place. run() finds diagram elements in a page and manages the surrounding browser work. They use related machinery, but they do not have the same job.
Before a parser sees the diagram
The source does not go directly from a text area to a grammar. Mermaid first prepares it. This preliminary work exists because the text carries more than the diagram’s visible relationships: it can include frontmatter, older directives, comments, titles, configuration, and formatting differences introduced by the host page.
The first step is normalization. Mermaid makes line endings consistent and adjusts certain HTML attributes into a form its parsing path expects. It then extracts YAML frontmatter when present. Frontmatter can provide a title and diagram configuration, such as a theme or other appearance choice. Older directive syntax is also recognized and removed from the text that the diagram grammar will parse. The result is not one undifferentiated string. Mermaid keeps the original source, a cleaned form with comments removed, and—when a diagram needs source-faithful positions—a form that still contains comments.
That distinction is easy to overlook, but it has a concrete consequence. If a parser reports that something is wrong on a particular line, the line should correspond to what the author sees, including any frontmatter or comments that affect positions. At the same time, most diagram parsers do not need comments in their input. Preprocessing lets Mermaid serve both needs without forcing every grammar to understand configuration syntax and editor-only material.
Configuration also has to be placed in the right scope before parsing and rendering proceed. Mermaid resets the active configuration, detects the likely diagram type, applies the diagram-specific scope, and then adds the configuration extracted from the definition. This allows a diagram type’s appearance defaults to participate while still allowing site-level and diagram-level settings to be resolved. The exact precedence is a separate concern, but the ordering principle is important: the runtime needs to know what kind of diagram it is handling before it can apply all of that type’s behavior consistently.
Next comes type detection. Mermaid removes frontmatter, directives, and comments from the text used for detection, then checks an ordered registry of detectors. Each detector asks whether the remaining text begins like its diagram language. The first successful detector wins. This is not just a convenience for selecting a label. It is the decision that determines which parser, database, renderer, and default behavior will be used.
The first-match rule makes detector order part of correctness. A broad detector placed before a more specific one can claim text that belongs to another diagram. Mermaid’s orchestration code therefore registers detectors in an intentional order and supports lazy loaders for definitions that are not needed until their syntax appears. A diagram that is never used does not have to be loaded into the active rendering path merely because the library supports it. Lazy loading can keep the initial bundle smaller, but it also means type recognition has to be reliable: a wrong match can load the wrong implementation, while a missed match produces an unknown-diagram error rather than a useful picture.
This is why parse() is useful even when no image is needed. It runs the preparation and type-selection path, constructs the diagram through its parser, and returns the recognized type and configuration if the definition is valid. With error suppression enabled, an invalid definition can produce false instead of throwing. An editor can use that result to mark a definition invalid before asking a renderer to alter the page. A documentation build can validate source separately from the step that creates browser output.
render() uses the same conceptual beginning but continues. It creates a rendering context, parses the definition, and asks the selected diagram renderer to draw. run() sits one level higher. It can search for elements with the Mermaid class, or accept nodes supplied by an application, then process them one by one. It extracts the diagram text from each element, calls the rendering path, replaces the element’s content with the returned SVG, and invokes any binding function after insertion. The three APIs are therefore different views of the same pipeline: check, produce, or manage a page-wide rendering operation.
From semantic model to browser output
Once Mermaid has selected a diagram type, it needs an internal model. The model is not yet an SVG and is not merely the original text. Diagram.fromText() obtains the registered definition, loading it if necessary, then clears the diagram database before parsing. It initializes the definition, connects older parser styles to the database when required, chooses the appropriate source form, encodes entities, and invokes the parser. The result is a Diagram object that carries the type, parsed text, database, parser, and renderer together.
The database is the state boundary between syntax and drawing. A parser records what it found there: nodes, relationships, labels, participants, tasks, or other structures appropriate to the diagram language. The renderer reads that state to decide what to draw. Keeping the state in a diagram-owned database means the renderer does not have to interpret raw syntax again, and it gives the architecture a place to hold semantic information before geometry is calculated.
Clearing that database before each parse is a correctness requirement. If a page contains two diagrams of the same type, data from the first must not appear in the second. A diagram definition can use a newly created database or a shared one whose clear() method resets all of its mutable state. The underlying rule is the same: one render must not inherit semantic content from another. This is a small implementation detail with a visible failure mode—ghost nodes, old titles, or unexpected edges in an otherwise valid picture.
Parsing errors are also part of this boundary. A valid diagram is not simply one for which the runtime did not crash. Mermaid’s parser interfaces are expected to reject invalid input with an error that a user can locate, and the standalone parser’s error type carries line and column information for lexer and parser failures. In a page integration, the public API can route such errors to a configured handler or suppress them according to the caller’s needs. If rendering encounters a problem, Mermaid can draw an error diagram unless the configuration says that error rendering should be suppressed. The choice affects the host’s user experience: a documentation page may prefer a visible failure marker, while an editor may want to retain control of the error display.
The next transformation is geometry. The semantic model says which entities and relationships exist; layout decides where they sit and how connecting lines travel. Mermaid’s shared rendering utilities measure text and other leaf elements, pass dimensions to a layout algorithm, receive positions and edge paths, and then let drawing code paint the shapes and labels. This separation keeps a diagram’s meaning distinct from the geometry needed to present it. It also explains why a parser can succeed while a diagram still needs layout work before it becomes readable.
Styles are added around the same time as drawing. Mermaid combines diagram-specific styles, the resolved theme, user-defined classes, and configuration-driven CSS. The renderer inserts those styles into the SVG and then asks the diagram renderer to draw the content. A theme therefore participates in the output before serialization; it is not a filter applied to an already finished image. The diagram’s shapes and labels need to be created in a way that the chosen theme can address.
The rendering context may be a caller-provided element or a temporary element that Mermaid creates for measurement and drawing. In sandbox mode, Mermaid performs its work in a sandboxed iframe. In ordinary rendering, it can place an SVG in a temporary root, measure what it needs, and remove that temporary structure after the result has been serialized. This temporary context is why an API caller can receive a string of SVG rather than having to surrender control of the page layout.
Before the result is returned, Mermaid adds accessibility information gathered during parsing. The SVG can receive a role description based on the diagram type, and diagram-provided titles and descriptions can become SVG title and description elements. The renderer then serializes the DOM representation into text. Serialization is the point where browser nodes become the artifact that a host can insert, store, or pass to another system.
Sanitization is part of that tail, not an optional afterthought. Unless the explicitly loose security level is in use, Mermaid sanitizes the serialized SVG with DOMPurify while allowing the controlled elements and attributes needed for its diagrams. Sandbox mode takes a different path by putting the SVG inside an isolated iframe. These choices affect the result’s capabilities. Stronger isolation can prevent scripts from running in the surrounding page, but it can also block interactions such as popups, links to other targets, or other features that depend on page context. The output is therefore shaped by both the diagram and the trust policy under which it is rendered.
The sequence shows why the word “render” hides several ownership changes. The author or host owns the source. Preprocessing owns normalization and configuration extraction. The detector chooses the language. The parser and database own semantic state. Layout and rendering own geometry and visual construction. The SVG boundary applies serialization and, depending on policy, sanitization or isolation. The host owns the final insertion and the timing of event binding.
The public render() result reflects that division. It returns the SVG string and may return a binding function, but it does not decide where the application should put the SVG. The documented integration pattern inserts the string first and then calls the binding function with the containing element. That ordering is necessary because event handlers need real DOM nodes to attach to. An application that calls the binding function before insertion has not completed the pipeline, even though it has received valid output.
The same boundary applies to fonts and dimensions. Mermaid can calculate layout in a browser context, but the host controls the surrounding page, available width, loaded fonts, and later CSS. A successful SVG string is evidence that Mermaid completed its rendering path; it is not proof that the image will occupy the intended space in every page. If the host uses an unexpected font or renders at a constrained width, labels can behave differently from what an author saw in an editor.
The pipeline also explains why a clean diagram can still be the wrong answer. Parsing validates that the definition follows a supported language. It does not validate the system described by that language. Layout can avoid overlaps and place edges, but it does not know whether an edge represents a relationship that exists in the real world. Sanitization can remove or isolate dangerous content, but it cannot decide whether a title accurately explains a diagram. Each stage completes its own contract; none of them can stand in for every other stage.
A useful way to read Mermaid is therefore to ask where a piece of information changes form. Text becomes configuration and a cleaned definition. The definition becomes a diagram-specific model. The model becomes measured geometry. Geometry and styles become SVG nodes. SVG nodes become a serialized artifact. The artifact becomes page content under a host’s security, font, sizing, and interaction rules. When a result is surprising, that sequence gives the maintainer a place to look.
This shared path is what allows Mermaid to support many visual languages without giving every diagram type a separate public API. The next architectural question is how those different languages enter the same path while retaining their own syntax and semantics.
References
- Mermaid repository
- Mermaid usage documentation
- Mermaid getting-started guide
- Mermaid syntax reference
- Mermaid community/contributing documentation
- Mermaid configuration documentation
- Mermaid theming documentation
- Mermaid accessibility documentation
- WAI-ARIA standards and guidelines
- Mermaid integrations