A jupyterlab mermaid diagram is a mermaid fence sitting in a notebook Markdown cell. Some JupyterLab versions or extensions render that fence as a picture. Some leave it as code. Check the version you run before you decide the syntax is wrong, and before you tell a teammate their notebook is broken.
I keep getting pulled into this by a screenshot. Someone pastes a flowchart into a cell, runs it, and sends me a picture of a gray code block. The fence is often fine. The install isn't drawing it. That's a different bug from a missing arrow, and mixing the two up wastes an afternoon.
What a Markdown cell actually holds
JupyterLab has two cells people confuse. A code cell is sent to a kernel. A Markdown cell is rendered by the notebook front end. Mermaid is not Python, so the kernel has nothing useful to say about it. The fence belongs in the Markdown cell, the same way a heading or a list does.
Inside that cell you write ordinary Markdown. A fence starts with three backticks and the info string mermaid, then a diagram keyword, then the statements, then a closing fence. The Mermaid in Markdown guide is the longer version of those fence rules. They don't change because the file happens to be an .ipynb. A notebook is a JSON wrapper around the same text.
The first line inside the fence is the diagram keyword. flowchart TD is a keyword. graph TD is the older spelling of a similar idea. A sentence is not a keyword. I once left a title on the first line, "Train and eval", and the parser stopped there. The title belongs in the Markdown above the fence, where a reader can see it even when the picture fails.
Blank lines inside the fence are usually fine. A comment that starts with %% is fine on its own line. What isn't fine is treating the cell like a design tool. You don't drag boxes. You edit text, you run the cell, and you look.
Paste a cell before you trust the install
Here is the smallest fence I use as a probe. It has one arrow. If this doesn't draw, a larger chart won't draw either, and rewriting the larger chart is a waste.
flowchart LR
write[Edit the Markdown cell] --> run[Run the cell]
run --> look{Boxes or a code block?}
look -->|Boxes| keep[Keep going]
look -->|Code block| ver[Check the JupyterLab version]Put that in a Markdown cell, not a code cell. Run it. If you see two boxes and a decision, this install can render a mermaid fence. If you see the backticks, stop debugging the arrows. You're looking at a renderer gap.
I have also pasted the fence into a code cell by muscle memory, because the shortcut for a new cell gives you code. The Python kernel then tries to parse backticks and throws a syntax error. That error is honest. The kernel was asked to execute Markdown. Change the cell type and run it again. Don't pip install anything until you've done that. I've installed a package to fix a cell that was simply the wrong type.
While you're in the cell, look at the info string. It should be lowercase mermaid. I've had a fence fail after I typed Mermaid with a capital M, because the renderer I was on matched the info string exactly and left the other spelling as a styled code block. Yours might be looser. If the probe shows code, the info string is the first thing to read, and it takes ten seconds.
Check the version you run
This is the part I want stated without softening. Some JupyterLab versions render a mermaid fence in Markdown. Some don't, until an extension is installed. Some extensions render it in the notebook UI and still leave it as code when you export. I am not going to name a version number and pretend it covers your machine. JupyterLab's release line and the extension you may have installed are two separate pieces of software, and they don't update as a pair.
Open Help, or whatever your build calls the about dialog, and read the JupyterLab version. Then open the extension manager and see whether anything mentioning Mermaid is enabled. Write both down in the notebook's first Markdown cell if this file is shared. "Works on my laptop" is how a lab meeting goes sideways. The person who cloned the repo is not running your extensions.
If the probe cell stays a code block, you have three honest options. Upgrade or add the extension your team has already agreed on. Or accept that this install shows source, and read the source. Or paste the fence into a viewer that you know renders it. Don't claim the diagram is "in the notebook" if half the team sees source. Say which install draws it.
Export is the other check. A notebook that looks right in the UI can become an HTML or PDF export where the fence is code again, because the export path doesn't always load the same renderer. I got burned by this on a methods page. The notebook on the projector had boxes. The HTML attached to the ticket had a code block, and the reviewer commented on the syntax as if it were the result. Run the export once before you send it. If the export drops the picture, ship a PNG next to the notebook and say so.
nbconvert, Jupyter Book, and a colleague's older JupyterLab are three more hosts. They can disagree with each other about the same file. The text of the fence is the portable part. The picture is a property of the viewer.
A train and eval split worth drawing
The flowchart I actually want in a notebook is the one that shows what happened to the rows. Not a metric. A metric belongs in a table computed from a run. If you type an accuracy into a node, you have invented a number that will drift from the next run and still look official. I've done that in a slide. It was embarrassing in the way that quiet bugs are embarrassing, because nobody could tell the number was stale.
Draw the split. The question the chart answers is: which rows are allowed to influence the fit, and which rows are only allowed to score it.
flowchart TD
raw[Raw rows] --> drop[Drop rows missing the label]
drop --> split{Assign each row}
split -->|Train| fit[Fit on the train rows]
split -->|Eval| hold[Hold the eval rows out]
fit --> score[Score the held-out rows]
hold --> scoreRead the arrows before you read the labels. raw goes through a drop step, then a split. The train branch goes into fit. The eval branch goes into hold, and hold meets fit only at score. There is no arrow from hold to fit. That missing arrow is the point. If you "simplify" the chart by pointing both branches at fit, you have drawn leakage, even if the code is careful. I did that once because the picture looked lopsided with a dangling holdout. Lopsided was correct. The pretty version was a lie.
The drop step is a real decision and it belongs on the chart. Dropping rows with a null label changes who gets scored. Hiding that step in a paragraph above the figure is how a reviewer misses it. If you impute instead of drop, change the label. Don't leave "Drop" on a node that fills values. The chart is a claim about the method. Make the claim match the function you called.
Direction is TD because this is a decision with two outs, and top-down is how people read a split. A left-to-right chart of the same nodes makes the holdout look like a later stage in a pipeline, which is a different story. The flowchart syntax page covers directions and node shapes if the split grows a third fold. You don't need that page for this chart. You need it when someone asks for a validation fold and the diamond gets a third edge.
Keep the node text short. "Drop rows missing the label" is about as long as I want. The column name, the fraction of rows, and the argument to dropna belong in the code cell under the figure. A node that quotes a pandas line becomes a column of wrapped text, and the arrow into it looks lost. The picture is the policy. The cell is the implementation.
If you want a first draft without hand-placing every id, the flowchart generator will sketch the split from a sentence like "drop unlabeled rows, fit on train, score on eval." Read the arrows after it drafts them. Generators like to connect every box to every other box. The leak is an extra arrow, and an extra arrow is easy to miss when the layout looks tidy.
Failures that are not the flowchart
A red parser message is different from a code block. A code block means the front end never treated the fence as a diagram. A parser message means it tried, and a line is illegal. Fix the line. Don't reinstall Jupyter over a missing colon.
The mistakes I keep seeing in notebooks:
- The diagram keyword isn't first. A Markdown heading got pasted inside the fence. Move it out.
- A node id is a reserved word.
endis the one that bites, because it's a natural name for the last step of a pipeline. Name that nodedoneorfinish. The wordendon its own closes blocks in other diagram types, and flowcharts are happier if you don't use it as an id. - A label contains parentheses or a slash and isn't quoted.
score[Score (eval)]can confuse the shape parser. Writescore["Score (eval)"]and keep the id boring. - Two diagrams got pasted into one fence. One fence, one keyword. The second keyword is a parse error, not a second picture. Use two Markdown cells, or two fences with a blank line between them.
- The chart is a code cell output you expected to be rich.
printof a string that happens to contain the word mermaid does not render. There is no kernel magic required for the Markdown path, and I won't invent one. If an extension documents a cell magic, follow that extension's own page. Don't assume this notebook has it.
Theme directives are another way to get a fence that works in one viewer and dies in another. Leave them out of the notebook until you have a reason. A default theme survives a lab machine in dark mode better than a pile of hard-coded fills.
The notebook should not be the only copy
I like notebooks for the exploration. I don't like them as the only place a method chart lives. The .ipynb diff is JSON. A one-word label change becomes a noisy patch, and reviewers skip it. The same fence in a Markdown file, or in a .mmd file whose first line is the keyword, reviews like code. The format of a .mmd file is the short version of that split: fence versus bare diagram source.
If the chart is part of the method, put it where the pull request can show it. The notebook can include the same fence so the person running cells sees it. Two copies will drift. I prefer one source file and a notebook that loads or pastes it, but most teams just duplicate the fence and forget. If you duplicate, say which copy wins. I've reviewed a paper where the notebook chart and the README chart disagreed about whether the eval rows were in the fit. Both rendered. Both were confident. The code matched one of them.
Other editors have their own preview quirks, and none of them tell you what JupyterLab will do. IntelliJ's Markdown preview is a JetBrains question. VS Code preview settings are a VS Code question. Typora is a third preview with its own build of the renderer. A fence that draws in one of those can still show as code in your notebook. That's not a contradiction. They're different programs.
When I want to know whether the syntax itself is valid, I leave the notebook. A Markdown Mermaid viewer will render the fence without asking JupyterLab to cooperate. If the viewer draws it and the notebook doesn't, the syntax is fine and the install is the variable. If both show a parser error, fix the text. That split has saved me from "upgrading" a lab image that was never the problem.
Hand the fence to a live preview
Notebook cell output is a snapshot from the last run. It doesn't follow the cursor. You edit, you run, you look. That's acceptable for a probe and annoying for a chart you're still reshaping, because each tweak costs a run and the output cell jumps.
Use the notebook to keep the fence next to the code it describes. Use a live preview when you're moving arrows. The editor renders as you type, with no signup, and you can paste the finished source back into the Markdown cell. Run the cell once so the notebook output matches what you pasted. Then check the JupyterLab version one more time if anyone else has to open the file. The picture is optional. The fence is the method.
Related posts
Frequently asked questions
Does stock JupyterLab render Mermaid?
Not in every install. If the fence stays as a code block, the notebook is not rejecting your syntax. The renderer is absent or off. Confirm against your version before you write a class handout.
Where should the source of truth live?
If the diagram describes a pipeline the team reviews, keep a copy in the repo as a markdown file too. A notebook output cell is easy to lose.
Can I export the notebook diagram as SVG?
Depends on the Jupyter setup. For a known SVG or PDF, paste the same source into an editor that states the format. SVG and PDF here are Pro.