jackvinati2k downloadsRender TikZ and LaTeX diagrams in your notes (even on iOS).
Draw TikZ and LaTeX diagrams in Obsidian notes: write the code in a tikz block and the note shows
the picture. pgfplots, circuitikz, chemfig, tikz-cd, forest and a hundred TikZ and pgfplots
libraries are built in. TeX runs inside the plugin, compiled to WebAssembly, so there is nothing
else to install, nothing is uploaded and nothing is fetched. It works offline, on desktop, iPhone
and iPad, and needs Obsidian 1.7.2 or later.
It is a fork of artisticat1/obsidian-tikzjax, which had not been updated since 2022. Version 0.2.1 sends a new diagram to the engine as soon as it comes into view, takes about a third off TeX's time on a desktop, and lets a diagram skip a preamble an earlier diagram has already compiled. Labels in Chinese, Greek, Cyrillic and other scripts are drawn as well.
```tikz
\begin{tikzpicture}
\draw (0,0) circle (1cm);
\node at (0,-1.6) {hello};
\end{tikzpicture}
```
Five of the plugin's test diagrams, as 0.2.1 draws them.
Grey is 0.2.0. Blue is 0.2.1 compiling the diagram from the start, as it does the first time it sees a preamble. Green is 0.2.1 drawing the same diagram a second time in a row, when it starts from the preamble it saved the first time. In Chromium, over the 37 test diagrams 0.2.0 was released with, TeX's time went from 2,827 ms to 1,850 ms from the start, 35% less, and each diagram took between 29% and 63% less. From a saved preamble the 37 took 752 ms. In WebKit, the browser engine of the iPhone and iPad, run on the same desktop, the same sums went from 4,210 ms to 2,772 ms, each diagram taking between 25% and 54% less, and to 1,192 ms from a saved preamble.
I measured this on my Windows desktop, in headless Chromium 153, which is what Obsidian's desktop
app is built on, and WebKit 26.6, through Playwright rather than inside Obsidian, with 0.2.0's
main.js as released beside 0.2.1's. Each figure is the fastest of seven runs, each run in a fresh
browser. Every test diagram 0.2.0 had draws as it did then, byte for byte, which CI checks; what
draws differently is labels in other scripts, which 0.2.0 left out, and the two fixes below.
The gain from the start is the engine. It is compiled so that TeX keeps the values it works on where
the processor reaches them directly, rather than in its memory; it is optimised with a newer
binaryen; and TeX's calls into the plugin, for every line and character it reads or prints, no
longer make new arrays each time. The gain from a saved preamble is that TeX does not load the
packages again: a diagram whose text up to \begin{document} is the same as an earlier diagram's
starts where that one stood at that point (see Questions).
| Test diagram | 0.2.0 (ms) | 0.2.1, from the start (ms) | 0.2.1, saved preamble (ms) | Change from the start |
|---|---|---|---|---|
arrows-meta | 44 | 28 | 6 | −36% |
basic (plain TikZ) | 18 | 12 | 4 | −33% |
batchmode-errors | 27 | 10 | 2 | −63% |
batchmode-missing-file | 12 | 8 | 0 | −33% |
broken (an error on purpose) | 14 | 9 | 2 | −36% |
chemfig | 64 | 45 | 17 | −30% |
circuitikz | 314 | 214 | 64 | −32% |
dieresis | 23 | 14 | 6 | −39% |
documentclass | 14 | 9 | 2 | −36% |
expl3 | 18 | 12 | 3 | −33% |
fatal-after-page | 14 | 9 | 2 | −36% |
feynhand | 359 | 235 | 158 | −35% |
filesize | 20 | 11 | 3 | −45% |
forest | 276 | 183 | 79 | −34% |
l-stroke | 21 | 13 | 5 | −38% |
mathfrak | 20 | 13 | 4 | −35% |
mathscr | 19 | 12 | 4 | −37% |
mathtools | 48 | 32 | 4 | −33% |
missing-package | 15 | 10 | 2 | −33% |
missing-pgfplots-library | 167 | 114 | 104 | −32% |
omega-otimes | 16 | 11 | 3 | −31% |
patterns | 42 | 26 | 3 | −38% |
pgfplots-axis | 296 | 189 | 77 | −36% |
pgfplots-fillbetween | 292 | 192 | 72 | −34% |
pgfplotstable | 234 | 155 | 33 | −34% |
rule-frac | 20 | 12 | 5 | −40% |
runaway-macro | 12 | 8 | 1 | −33% |
shading | 22 | 14 | 7 | −36% |
siunitx | 133 | 89 | 10 | −33% |
tikz-cd | 104 | 64 | 30 | −38% |
tilde | 19 | 12 | 5 | −37% |
ts1-currency | 25 | 16 | 8 | −36% |
ts1-small-caps | 22 | 14 | 6 | −36% |
ts1-symbols | 23 | 15 | 7 | −35% |
utf8-names | 22 | 14 | 6 | −36% |
white-fill | 17 | 12 | 4 | −29% |
xparse | 21 | 14 | 4 | −33% |
| All 37 | 2,827 | 1,850 | 752 | −35% |
TeX's time only, fastest of seven runs, in milliseconds. missing-pgfplots-library stops in its
preamble, so it saves nothing to start from.
A new diagram also waits before TeX starts on it: for the plugin to hand it to the engine, and for the engine to start.
In 0.2.0 a diagram that was not cached waited 300 ms on a computer, and 500 ms on a phone, before it went to the engine, so that typing in a diagram would not compile it at every keystroke. It waited also when you had just opened the note. 0.2.1 keeps that wait only for a note you are typing in. The engine starts in a third of the time, and as soon as a diagram turns out not to be cached rather than when it is about to be compiled. The first diagram after the engine starts takes a few milliseconds longer than in 0.2.0, because it saves its preamble for the diagrams after it. Added up, the first new diagram of a session spent about 500 ms on these three in 0.2.0 and about 100 ms in 0.2.1, before the plugin's SVG pipeline and drawing it in the note, which this harness does not include. After it, a small diagram with the same preamble takes 7 ms instead of 24, because it starts from the saved preamble, and 14 with saved preambles off; the harness timed the same diagram drawn a second time. The wait is the plugin's setting; the other figures are medians of the same seven runs, in Chromium, and a diagram's time runs from handing it to the engine to its SVG back.
On my iPhone 16 Pro, on iOS 27.0.1 with Obsidian 1.14.5, 0.2.0's engine started in 134 ms and a small diagram took 15 to 40 ms of TeX after the 500 ms wait, read off the diagnostics view on 14 October 2026. I will add 0.2.1's figures from the same phone here once I have read them off it the same way; until then, every figure on this page that compares 0.2.1 with 0.2.0 is from the desktop.
Also in 0.2.1:
text width node Chinese and Japanese wrap between characters. What text drawn this way cannot do
is under Known limitations. RepentStar contributed it, in
pull request #2.main.js beside a new manifest.json, the plugin says so once,
from 0.2.1 on (below).After updating from 0.2.0, only the diagrams with a character the engine has no font for, such as Chinese, Greek or Cyrillic, and those under a preamble file that ends with a comment and no newline, are compiled again; every other diagram comes from the cache as before. Everything that changed is in the changelog.
In Obsidian 1.7.2 or later: Settings → Community plugins → Browse, search for TikZJax Next, install, enable.
To install by hand instead, download main.js, manifest.json and styles.css from a
release into
<vault>/.obsidian/plugins/tikzjax-next/ and enable it in Settings.
Disable the original TikZJax plugin first if you have it. The two have different plugin ids, so
they can be installed side by side, but both register the tikz code block and would render every
diagram twice. This plugin checks for that and refuses to start rather than doing it quietly.
If you had the original installed, its rendered diagrams are reused instead of being recompiled. Its cache is only read, never modified, so it keeps working if you go back to it.
A tikz block holds what a LaTeX file would, minus the \documentclass line: the engine starts
from the standalone class with TikZ and xcolor already loaded, and a second class is an error. A
block that is only a tikzpicture is wrapped in \begin{document} … \end{document} for you.
To load packages or libraries, write them the way a LaTeX file would, ahead of an explicit
\begin{document}:
```tikz
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}
\usepgfplotslibrary{fillbetween}
\begin{document}
\begin{tikzpicture}
\begin{axis}[width=8cm, height=5cm]
\addplot[name path=a, domain=0:3] {x};
\addplot[name path=b, domain=0:3] {0.5*x^2};
\addplot[gray!40] fill between[of=a and b];
\end{axis}
\end{tikzpicture}
\end{document}
```
…or name them in a %!tikz line (below) and keep the block to the picture. A diagram renders when
it scrolls into view; one you have seen before appears instantly, from the cache, in the same frame
as the text around it. When TeX reports an error, the block shows it with its line (or, for an
error in a preamble file, that file and its line) and, where it can, what to change; a mistyped
macro usually still renders the rest of the diagram.
Accented letters work as typed, dieresis and Ł included (Grüße, naïve, \"u, Łódź), also in node
names, style names and \label keys. So do ° × ± µ → £ € © ®, \$, list bullets
and siunitx's units; see the limitations below for the characters that do not.
test/vault/TikZ-smoke-test.md is a note full of examples to copy
from.
Options go in the block body, as %!tikz lines. They are stripped before the source reaches TeX.
```tikz
%!tikz width=420 align=left colors=paper alt="RC low-pass filter"
%!tikz packages=circuitikz libraries=arrows.meta
\begin{circuitikz}
\draw (0,0) to[R=$R$] (2,0) to[C=$C$] (2,-2) -- (0,-2) -- (0,0);
\end{circuitikz}
```
| Key | Values |
|---|---|
width, max-width, scale |
CSS length, or a number for scale |
align |
left, center, right |
alt |
quoted text, used as the accessible name. alt="" marks a diagram decorative |
colors |
adapt (default), preserve, paper, invert |
lazy |
on, off, manual |
timeout |
seconds, or a duration such as 500ms |
packages |
comma list, name[options] accepted |
libraries |
comma list, passed to \usetikzlibrary |
border |
a TeX length, adds a margin around the diagram |
preamble |
a .tex file in the vault, put in front of the block. See below |
twopass |
runs TeX a second time when the first run left something to re-read, so \label and \ref resolve. Off by default: it can double the compile time |
wrap |
auto (default) wraps the block in \begin{document} … \end{document} unless it has one; always and never decide for it |
fast |
skips SVG optimisation and the pre-flight check, and renders ahead of other diagrams |
svgo |
off leaves the engine's SVG as it is; targeted and the default preset simplify how text is placed and drop an attribute browsers disagree on |
raw |
runs only the mandatory pipeline stages |
nocache |
skips the cache in both directions |
They are not written in the code fence line (```tikz width=420). Obsidian cannot read the fence
tail during PDF export, in embeds or in hover previews, so the same block would hash to two
different cache keys and the PDF would get a differently compiled diagram.
The plugin's settings cover colours, lazy rendering, fast mode, a second engine on a computer, saved
preambles, the time limit, how the source is cleaned up, the pre-flight checks, the preamble files,
the TeX log and the cache. A change applies to the open notes straight away, also when it arrives
from another device through Sync. Where an option shares its name with a setting (colors, lazy,
timeout, fast) the option wins for its block.
Macros, styles and packages that many diagrams use can live in a .tex file in the vault instead
of being repeated in every block. A preamble goes where a LaTeX preamble goes, before
\begin{document}, so it holds \usepackage, \usetikzlibrary, \newcommand, \tikzset and
\pgfplotsset.
latex/diagrams.tex:
\usetikzlibrary{arrows.meta, positioning}
\tikzset{box/.style={draw, rounded corners, inner sep=6pt}}
\newcommand{\vect}[1]{\mathbf{#1}}
A note:
```tikz
%:input latex/diagrams.tex
\begin{tikzpicture}
\node[box] (f) {$f(\vect{x}) = A\vect{x}$};
\node[box, right=of f] (g) {$g$};
\draw[-Stealth] (f) -- (g);
\end{tikzpicture}
```
Four things can name a preamble file. Every one that applies is used, in this order, so a later file can use what an earlier one defines:
.tex, .sty, .ltx, .def and .txt files as you type; the path takes
effect when you pick one, press Enter or leave the field.tikz-preamble.tex in the note's folder, or failing that the
nearest folder above it, up to the root of the vault. Only the nearest one is used. The name is
Settings → Preamble → Folder preamble file name, which takes effect the same way; empty turns
the search off.%!tikz preamble=path/to/file.tex in the block.%:input path/to/file.tex lines in the block, in the order they are written. A preamble file
can %:input other files as well.Paths resolve the way a [[link]] does: relative to the file that wrote them (the note, or the
preamble file for a nested %:input) and otherwise by name anywhere in the vault. A file named
twice is included once. A file that cannot be found, or a loop of %:inputs, shows a note under the
diagram saying which file and who asked for it; nothing is silently left out. Inside a preamble file
only %:input lines are read as directives: a %!tikz line there is a TeX comment and changes
nothing.
The contents of every preamble file are part of the cache key of each diagram that uses it. Edit the file and those diagrams, and only those, are compiled again the next time they render; a stale one is never served from the cache. Open notes re-render straight away, in Reading view and in Live Preview. When TeX stops on a line of a preamble file, the error card names the file and the line.
| Command | What it does |
|---|---|
| Copy the diagram at the cursor as SVG | Puts the rendered SVG on the clipboard, theme-neutral, so it opens correctly outside Obsidian. |
| Save the diagram at the cursor as an SVG file | Writes it to your attachment folder. |
| Finalize the diagrams in this note | Saves each diagram as an attachment and rewrites the note as ![[diagram.svg]] with the original fence preserved verbatim inside an Obsidian %% comment. The point is that the note now renders without this plugin: in Publish, in another editor, on GitHub. |
| Un-finalize the diagrams in this note | The exact inverse. The source was never thrown away. |
| Render all diagrams in this note | Ignores lazy rendering and compiles everything now, with a Stop button, and says how many rendered. |
| Open the diagram at the cursor in a zoom view | A scrollable, zoomable view of one diagram. |
| Open TikZ diagnostics | The engine, the cache, and each recent diagram: how it came out, where its time went from coming into view to drawn, and how often the engine started. Its Copy button copies the versions, the device, the engine's starts and the timings as text for a bug report, without your diagrams' code, their error messages or the names of your notes. Start here when a diagram does something inexplicable. |
| Clear the diagram cache | Forgets every diagram this plugin has rendered, and deletes the stored preambles; each diagram compiles again when it is next shown. |
| Restart the TeX engine | Stops the engine, which starts again with the next diagram, and lets diagrams that failed in this session try again. The next start picks up the stored preambles again: should a preamble draw wrongly, turn off Settings → Saved preambles instead. |
The commands about the diagram at the cursor, and Finalize and Un-finalize, need the note open in
the editor, in Live Preview or Source mode; they are not offered in Reading view. Finalize leaves
alone a diagram in a blockquote or a callout, and one whose fence starts on the list item's own
line (1. ```tikz); copy or save those as SVG instead.
The engine is e-TeX with a LaTeX format, built from TeX Live 2023 as Ubuntu 24.04 packages it. The main packages, with the versions the engine reports (Settings → Bundled TeX engine shows them for the installed build):
| Package | Version |
|---|---|
| pgf/TikZ | 3.1.10 |
| pgfplots, pgfplotstable | 1.18.1 |
| circuitikz | 1.6.6 |
| chemfig | 1.66 |
| siunitx | 3.3.10 |
| tikz-cd | 1.0 |
| amsmath | 2.17o |
| expl3 | 2024-01-22 |
| xparse | 2023-10-10 |
| standalone class | 1.3b |
0.2.0 and 0.2.1 change the engine, not the packages: these are the versions 0.1.5 had. Newer ones come with TeX Live 2026 in 0.3.0.
Loaded before your block starts: the standalone class, tikz (with pgf) and xcolor. Besides
those, there are 42 packages (counting the ones the others load), 84 TikZ libraries and 16 pgfplots
libraries, from 247 TeX files. The lists below are taken from the build itself, and a test keeps
them in step with it.
Packages, for \usepackage or %!tikz packages=, including the ones the others load:
amsbsy amsfonts amsgen amsmath amsopn amssymb amstext array atveryend
atveryend-ltx calc chemfig circuitikz color elocalloc environ etoolbox eufrak
expl3 forest hf-tikz ifthen infwarerr inlinedef keyval ltxcmds mathrsfs
mathtools mhsetup pdftexcmds pgfcalendar pgfopts pgfplots pgfplotstable preview
siunitx tikz-3dplot tikz-cd tikz-feynhand translations trimspaces xparse
TikZ libraries, for \usetikzlibrary or %!tikz libraries=:
3d angles animations arrows arrows.meta automata babel backgrounds bbox bending
calc calendar cd chains circuits circuits.ee circuits.ee.IEC circuits.logic
circuits.logic.CDH circuits.logic.IEC circuits.logic.US curvilinear datavisualization
datavisualization.3d datavisualization.barcharts datavisualization.formats.functions
datavisualization.polar datavisualization.sparklines decorations decorations.footprints
decorations.fractals decorations.markings decorations.pathmorphing
decorations.pathreplacing decorations.shapes decorations.softclip decorations.text er
external fadings feynhand fillbetween fit fixedpointarithmetic folding fpu
graphs graphs.standard intersections lindenmayersystems math matrix mindmap
patterns patterns.meta perspective petri plothandlers plotmarks positioning quotes
rdf scopes shadings shadows shapes shapes.arrows shapes.callouts shapes.gates.ee
shapes.gates.ee.IEC shapes.gates.logic shapes.gates.logic.IEC shapes.gates.logic.US
shapes.geometric shapes.misc shapes.multipart shapes.symbols snakes spy svg.path
through trees turtle views
pgfplots libraries, for \usepgfplotslibrary:
clickable colorbrewer colormaps colortol contourlua dateplot decorations.softclip
fillbetween groupplots patchplots polar smithchart statistics surfshading ternary
units
Being in the list means the file is there, not that everything in it can work: contourlua needs
LuaTeX and external needs to run programs, and this engine has neither. What a fixture in
test/fixtures/tex renders end to end through the shipped worker, on every CI run: plain TikZ,
pgfplots including its libraries, pgfplotstable, circuitikz, chemfig, tikz-cd, forest,
mathtools, xparse, siunitx, tikz-feynhand, arrows.meta, shadings, ~, letters with a
dieresis, Ł and ł, accented node and style names ((café), grün), ° × → ± µ
£ € $ © ® ª º with their text commands, labels in Chinese, Greek and Cyrillic beside
node, style and colour names in them, and Chinese and Japanese wrapped in a text width node. The
patterns library loads too, but see the limitations below.
\mathfrak, \mathscr |
TeX compiles them, and then the diagram fails: the DVI-to-SVG converter has a fixed font table, and it has neither the Euler fonts nor rsfs. For \mathfrak the Euler face and its metrics both ship, so the fix is in @drgrice1/dvi2html. \mathscr (rsfs) has only its metrics bundled and no face at all, so it would need a face built as well as a converter change. |
patterns draws nothing |
The driver emits the pattern reference but not the pattern definition. |
chemfig's \schemestart loses bonds |
The bonds are already missing in the DVI, before any JavaScript runs. |
tikz-feynman |
LuaTeX only. tikz-feynhand is bundled and works. |
| Greek, Cyrillic, CJK, Arabic, Hebrew, IPA, emoji | Not in this engine's fonts, so TeX sets each such character in a label as a box one em wide, and the plugin writes it over the box in your system's sans-serif font. It renders, but not in Computer Modern. Most accented Latin letters are typeset by TeX itself (Dvořák, Erdős, İstanbul, Łódź, café, Grüße, naïve), as are curly quotes, dashes and …; the exact set is measured from the engine and listed in src/source/native-unicode.ts. Nodes, coordinates, styles, colours and \labels can be named in any script. In typewriter type Ł and ł show a ␣ where the stroke should be, because that font has one in its place. |
\textyen, \textperthousand, \textcircled and the rest of TS1 |
They come from the TS1 text companion font, which the SVG converter cannot draw, and the diagram fails. Typed as ¥ ¢ ‰ ½ №, those characters are drawn in sans-serif like the scripts above. ° × ± µ · ÷ ¬ § ¶ † ‡ • ™ ℃ ← ↑ → ↓ Ω and ² ³ ¹, their \text… commands, siunitx's units (\degree, \micro, \ohm and the rest), \S, \dag and list bullets do render: they are drawn from the math fonts instead, so µ comes out as the italic math μ. So do £ € © ® ª º, their \text… commands, \pounds, \copyright and \$, built from the Computer Modern text fonts, mostly as older LaTeX releases built them: € is a C with a small = across it, LaTeX's own stand-in for a font without a euro, and in bold type £ and ® stay medium weight. |
| Text drawn in sans-serif | It sits in the space TeX set aside, one em for each character, so a word in Cyrillic or Greek takes more room than it needs, and a long one can stick out of a narrow text width node. In a text width node or a \parbox, a line breaks where a space is typed, and Chinese and Japanese also between two characters, though not before closing punctuation or a small kana, nor after an opening bracket. It keeps the size and colour TeX gave it, but not bold or italic, and in a sub- or superscript it is full size. In maths, a typed α ∈ ∞ is drawn the same way, upright; TeX's own $\alpha$ $\in$ $\infty$ give Computer Modern's. Characters set side by side, or with only spaces between them, are drawn as one piece of text, so a phrase in Arabic or Hebrew reads in the right order, unless a number or a Latin word in it splits it. An accent typed as a separate combining character after its letter is drawn beside it, so type the accented letter itself. With Source handling at Corrected, the default, the plugin removes invisible characters from every block, the zero-width joiner and non-joiner among them: an emoji built with joiners comes out as its parts, and a Persian, Urdu or Indic word spelled with a non-joiner is joined where it should not be. Text outside the picture, such as \chemname's name under a molecule, is not drawn. |
| Sans-serif text in a copied or saved SVG | The SVG names the font sans-serif and does not embed it, so another program draws that text in its own. |
Obsidian Sync Standard does not sync main.js |
It is 12.2 MB (11.7 MiB), and that plan caps a file at 5 MB. The plugin still installs from the community browser or a release on every device; only syncing the plugin folder itself is affected. The size is the TeX engine (the 22 MiB of its 156 MiB core dump that holds anything, gzipped and base64'd), and shrinking it means a smaller engine, not tighter code. TeX Live 2026 in 0.3.0 should bring it to about 8.3 MB, still over the limit. |
| Obsidian Publish | Publish runs no community plugins. Save the diagram as an SVG attachment, or finalize the note, and the result publishes as ordinary content. |
| Platform | State |
|---|---|
| Windows, macOS, Linux | Works on Windows, which is what I use and where every desktop figure above was measured. macOS and Linux run the same code, but I have not tried them. |
| iPhone and iPad | Works: the engine compiles and diagrams render on the device, as checked on an iPhone (below). It is slower than on a desktop, and the first diagram that is not cached is the slowest, because it starts the engine. |
| Android | Not tried yet. A report either way is welcome, in Q&A or as an issue. |
My iPhone is an iPhone 16 Pro on iOS 27.0.1, with Obsidian 1.14.5. On it I have checked that 0.1.5 and 0.2.0 load and render and that Settings opens, and with 0.1.5 that a diagram still compiling when you switch away from the app finishes when you come back, instead of failing with a timeout. 0.2.1's betas, from beta.2 to beta.4, load and render on it too, and the diagnostics view's Copy button copies its text. The phone figures on this page are read off that view. With 0.2.0 I also timed diagrams with Lockdown Mode on and off (Questions). The iPad runs the same build; I have not checked it separately. 0.2.1's engine uses only WebAssembly 1.0, as 0.2.0's did, so it compiles in the same WebViews as before, iOS 14.5's among them; a check in CI decodes the engine to make sure. Before iOS 16.4, which has no decompression of its own for the engine to use, the engine unpacks itself in JavaScript, as 0.2.0 did.
Not tried there yet: a note with twenty diagrams that are not cached, and PDF export on the phone. If something gives out on a phone, it is probably one of those, and an issue with the device, the iOS version and the text the diagnostics view's Copy button gives helps more than anything else. Questions explains how to get that text, and then the console output from an iPhone with Safari on a Mac.
Every push to main, every pull request and every release runs the same checks: over 1,400 unit tests, the community store's lint rules, type checks with two versions of TypeScript, a check that the plugin makes no network requests, and all 44 TeX test diagrams through the real engine. Each has to give, byte for byte, the DVI, transcript and SVG recorded for it (or, where none was recorded, none), so an engine change cannot alter any of them unnoticed. Each test diagram, and 33 cases written to break saved preambles, must also come out the same, byte for byte, when it starts from a saved preamble, kept in memory or read back from storage, as when it is drawn from the start; and the memory TeX is handed before each diagram must be the same as a new one. The build refuses syntax that older iOS Safari cannot parse.
The plugin is built with TypeScript 7, esbuild 0.28, ESLint 10 and Vitest 5, against the Obsidian 1.14 typings, and Dependabot checks its dependencies every week.
Releases are reproducible: building the tagged commit gives the same main.js on any machine, and
the community store checks exactly that. A release runs all of CI again on the tag and is published
with build provenance attestations. The TeX engine is built in a container from pinned upstream
sources, and a weekly job rebuilds it and checks that it matches the copy in the repository.
0.1.0 came out on 1 September 2026, and 0.1.5 and 0.2.0 on 13 October. Every version goes through the community store's review again, so I would rather put out fewer, bigger releases. The order can change with what people ask for.
\hreac and three years of fixes. main.js should shrink
from about 12.2 MB to about 8.3 MB, by an estimate from a test build. TeX itself will not get
faster: in my tests, on the engine before 0.2.0, the total was a few percent slower and every
small diagram 10 to 20 ms slower. The hash-table change in 0.2.0 wins back most of the total, but
not the cost on small diagrams. With the new packages too, a diagram drawn from a saved preamble
has to come out byte for byte as one drawn from the start, which CI checks.![[file.tex]] embeds rendered in
place (planned, issue #1), .sty and
.csv files from the vault, Copy as PNG and syntax highlighting in tikz blocks. Labels in other
scripts, which were on this list, came in 0.2.1, from
pull request #2.The plan in full is on the wiki's Roadmap. To ask for something, or vote for what is already there, use Discussions → Ideas: a thumbs-up counts as a vote.
Do I need LaTeX installed? No. The whole engine is inside the plugin and runs offline.
Why does the first diagram take a while? It starts the engine. On my desktop, in headless Chromium, the browser engine of Obsidian's desktop app, that takes about 54 ms with 0.2.1, where 0.2.0 took about 160 ms. On my iPhone 16 Pro, on iOS 27.0.1, 0.2.0's engine started in 134 ms, read off the diagnostics view on 14 October 2026, with Obsidian excluded from Lockdown Mode (below). The first diagram of each preamble also saves it, which costs it a few milliseconds and spares the diagrams after it their packages. Later diagrams in the session are faster, and a diagram you have already seen comes from the cache without running TeX at all.
Why is the second diagram with the same packages faster? Since 0.2.1 the engine keeps where TeX
stands once a diagram's \begin{document} is done, with its packages and preamble loaded, and a
later diagram whose text is the same up to there starts from that point and compiles only its body.
"The same up to there" is everything the plugin puts in front of the block: the packages and
libraries in its %!tikz line or the settings, the global preamble, a preamble file, and the
block's own lines before \begin{document}. Blocks that are only a tikzpicture all share one,
except a block with a character drawn in sans-serif, or with one of the stand-ins for ° × £
and the like: those add to what comes first, so they share one only with blocks that add the same.
On a Windows desktop, in headless Chromium and WebKit through Playwright rather than in Obsidian,
drawn a second time in a row, a plain TikZ diagram took 4 ms of TeX where it took 12 from the start
in Chromium, the engine of Obsidian's desktop app, and circuitikz 64 where it took 214; in WebKit,
the engine of the iPhone and iPad, 6 instead of 17 and 105 instead of 318. The first diagram of each
preamble takes a few milliseconds more, to save it, and about a tenth of a second without the JIT
(below). An engine keeps up to 32 MB of saved preambles in memory on a computer and 12 MB on a phone
or tablet, or on a computer that reports less than 8 GB of memory. In 12 MB a large preamble does
not always fit beside the ones in use, and is then not kept there. A diagram drawn this way is the
same, byte for byte, as one drawn from the start, and every release checks that for every test
diagram; a preamble that asks the size of the diagram's own file, or reads it, is never saved. The
diagnostics view says of each diagram whether it started from a saved preamble.
Do saved preambles survive a restart? Yes. The engine stops when it has had nothing to do for a while, on a phone after 15 seconds and whenever Obsidian goes to the background, and the preambles it holds in memory go with it. So each one is also kept in this device's own storage, in Obsidian's IndexedDB beside the diagram cache: never in your vault or the plugin's folder, so it is never synced with the vault, by Obsidian Sync or iCloud Drive, and each device keeps its own. When the engine starts again, after a stop or after Obsidian restarts, a diagram whose preamble is stored starts from it instead of loading its packages again. On a Windows desktop, in headless WebKit with a phone's budgets rather than in Obsidian, the first diagram after a fresh start of the browser, the engine's start included, took 223 ms for circuitikz from its stored preamble where it took 426 with the preamble compiled, and 153 for pgfplotstable where it took 358; without the JIT, circuitikz took 2.8 s instead of 8.2.
A stored preamble is where TeX stood once everything in front of a diagram's \begin{document} was
read: the packages and libraries, the global preamble, the folder's tikz-preamble.tex, any
preamble= or %:input file and, in a block with a \begin{document} of its own, the block's
lines before it, with their text as it was read, what TeX printed while reading them, and any file
it had written by then. The diagram itself is not in it. It is compressed where the system can, and
not encrypted, like the diagram cache. Stored preambles take up to 64 MB for each vault on a
computer, and 16 MB on a phone or tablet and on a computer that reports less than 8 GB of memory;
when that is full, the preamble used least recently goes, one that took longer to load counting as
used a little later, and until then one stays, also after you
edit a preamble file, whose new text is stored as another preamble. A stored preamble belongs to one
build of the engine, and an update with another one deletes a vault's old ones, unread, the next
time its engine starts. Clearing the diagram cache (Settings → TikZJax Next → Cached diagrams →
Clear, or the command) deletes them, and so does turning saved preambles off. Obsidian tells a
plugin nothing when it is disabled or uninstalled, so to remove them and the cached diagrams from a
device, clear the cache before you remove the plugin. Where storage is not available, as in some
private or restricted setups, preambles are kept only while the engine runs, as before; where it is
full, the ones stored are still used. The diagnostics view counts the storage errors, shows how many
preambles are stored and how much room they take, and says of a diagram that started from one
"resumed from a stored preamble", with how long reading it and putting it back took.
How do I turn saved preambles off? Should a diagram ever draw differently because of them, turn off Settings → TikZJax Next → Saved preambles, on a phone as on a computer; from the next diagram on, every diagram is compiled from the start, as in 0.2.0, and the preambles stored on that device are deleted. Please open an issue with the diagram if that changes how it draws.
Can I put TikZ in a $$ … $$ math block? No. Obsidian hands math blocks to MathJax before any
plugin sees them, and MathJax does not run TikZ. Use a tikz block; for an inline-looking result,
%!tikz align=left and a small width.
My block starts with \documentclass and shows an error. Remove the line. The engine already
has its document class loaded, so LaTeX reports a second one ("Two \documentclass or \documentstyle
commands"), and \usepackage lines followed by \begin{document} work as they would in a LaTeX
file.
A plot of tan, 1/x or exp fails with "Dimension too large". pgfmath works in fixed point
and overflows near an asymptote or a large value, the same way it does in a real LaTeX
installation. Restrict the domain, or skip the region around the asymptote.
Can I use tikz-feynman? It needs LuaTeX, which this engine is not. tikz-feynhand is bundled:
it is derived from tikz-feynman, with the vertices placed by hand. %!tikz packages=tikz-feynhand.
Is package X available? See What is bundled. If it is not there, the
diagram tells you with LaTeX's File `X.sty' not found. If the diagram never uses the package,
as with inputenc or fontenc, which are not needed here, nothing else changes and the line can
go. For a package you do need, open an issue with the package request form and a diagram that needs
it.
Can I use the diagrams outside Obsidian? Copy or save one as SVG with the commands above, or finalize the note: each diagram becomes an SVG attachment and the source stays in the note, so it shows up in Publish, on GitHub and in any Markdown editor.
Something fails on my iPhone. How do I report it? First run "Open TikZ diagnostics" from the command palette, press Copy, and paste the text into the issue: it has the versions, the device, the engine's starts and where each diagram's time went, and none of your diagrams' code or note names. There are no devtools on the device, but after that, if you have a Mac, Safari can reach its console: on the device, Settings → Apps → Safari → Advanced → Web Inspector; connect it to a Mac by cable; then in Safari on the Mac, Develop → the device → the Obsidian window. Every mobile bug report on the original repository is untriageable for want of this paragraph, so if you are filing one, this is how to get something worth attaching.
Diagrams are very slow on my iPhone or iPad. See whether Lockdown Mode is on: Settings → Privacy & Security → Lockdown Mode. It runs Obsidian's web content without the JIT compiler, and the TeX engine with it, which makes TeX twenty to thirty times slower. On my iPhone 16 Pro, on iOS 27.0.1 with 0.2.0, on 14 October 2026, a circuitikz diagram took 7.3 s of TeX with Lockdown Mode on and 0.27 s with Obsidian excluded from it, 27 times as long, and the engine started in 1.3 s instead of 0.13 s. Lockdown Mode can stay on for everything else: under Configure Web Browsing in its settings you can exclude Obsidian alone, then restart Obsidian. The exclusion takes Lockdown Mode's protection away from Obsidian's web content, so whether it is worth it is your call. On some versions of iOS, Lockdown Mode turns WebAssembly off altogether: then the engine does not start at all, the diagram's card says that the device runs no WebAssembly, and on some of those versions excluding Obsidian has been reported not to bring it back.
Low Power Mode, a phone that is hot, and a nearly empty battery slow the whole phone down, the plugin with everything else, though not by as much. On my iPhone 16 Pro, on iOS 27.0.1, in Low Power Mode with the battery at 18% on 15 October 2026, 0.2.1-beta.4 took 867 ms of TeX for circuitikz and 749 for pgfplots, where beta.3 had taken 231 and 211 the day before; one reading each. The engine's unpacking, which does not use the JIT, took more than three times as long too, so it was the phone that was slow, and there was nothing to change in Obsidian.
The diagnostics view tells the two apart: run "Open TikZ diagnostics" from the command palette and read its JIT line. "on (probably)" means TeX runs with the JIT, and a slow diagram is the phone being slow. "off (probably)" means it runs without, most likely because of Lockdown Mode. "unclear" means the engine's test could not tell, and "checking again" that it is taking the test once more, a second after the engine started, so that a phone that is only slow is not taken for one without the JIT. "no WebAssembly" means the engine cannot run at all. When two engine starts in a session find the JIT off on an iPhone or iPad, a notice says so once.
After an update, another device shows the new version but nothing changed.
Obsidian Sync Standard does not copy a file over 5 MB, and main.js is about 12 MB. When Sync
carries your installed plugins, the other device gets the new manifest.json, which names the new
version, and keeps the old main.js; Obsidian and BRAT then see the new version number and offer
no update. With Sync Standard, turn off "Installed community plugins" in Sync's settings and update
the plugin on each device. To repair a device already in this state, reinstall the plugin there:
with BRAT, its "Choose a single plugin to reinstall"; from the community store, uninstall it and
install it again, which also resets its settings on that device. Since 0.2.1 a device in this state
says so once, and the diagnostics view shows both versions, but only when its main.js is 0.2.1
or later: 0.2.0's cannot tell.
Where can I ask something, or suggest a feature? In Discussions: Q&A for help with a diagram, Ideas for the rest.
The original plugin created a <script type="text/tikz"> element and let a bundled script scan the
document for it. That meant it held no handle on anything it started, so per-diagram timeouts,
cancellation, caching, error reporting and PDF export were not so much unimplemented as
unreachable. This fork owns the whole path from the code fence to the SVG.
Cached diagrams appear instantly. A diagram you have seen before is a hash lookup and one string replacement, done before the code block processor returns, so it paints in the same frame as the surrounding text with no spinner and no layout shift. Diagrams are cached by a hash of their source, so nothing recompiles unless you change it. Switching theme, resizing a diagram or changing its alignment costs no recompilation at all, because none of those are part of the cache key.
Errors say what went wrong. The old plugin's only failure path was a broken-image icon (and an HTTP request to a third-party domain on every failure, in a plugin whose selling point was working offline). This one reports the TeX error and the line, and where possible what to do about it. A mistyped macro usually renders the rest of the diagram and tells you what it dropped.
A bad diagram cannot take the rest with it. TeX is fed \nonstopmode, so an error is logged
instead of stopping at an interactive prompt. If a diagram exceeds its time budget the worker is
terminated and restarted, and only that diagram fails. In the original, one bad diagram blocked
every later one until Obsidian was restarted.
Diagrams render when they scroll into view, off the editor's thread: one at a time on a phone, and two at a time on a computer while several are waiting, each in an engine of its own, unless the second engine is turned off or the computer reports little memory. A note with thirty of them does not lock up the editor.
PDF export waits for the diagrams. The processor returns a promise, which is what Obsidian actually awaits before taking the print snapshot, for at most a minute in all, after which a diagram not yet compiled is printed as a short note saying so; "Render all diagrams in this note" beforehand makes sure there is none. Colours are pinned to black on white for print regardless of your theme.
Dark mode is handled in CSS, not by rewriting the SVG. The old plugin string-replaced black
and white inside the markup, which could not tell TeX's default ink from a colour you chose, and
baked CSS variables into presentation attributes so a copied SVG rendered wrong outside Obsidian.
Here the stored diagram is theme-neutral and CSS does the work.
The TeX engine is built from source, in a container, from pinned upstream commits, rather than vendored as an opaque 7 MB file. That is what made it possible to find out what is actually inside it, and to fix things that were previously assumed to be immovable. 0.2.0 and 0.2.1 change that build: asyncify taken out, TeX's macro names spread over more of its hash table, three files of web2js's code generator replaced so that TeX's local variables are WebAssembly locals, and four TeX change files of its own, one of them the point where a diagram's preamble is saved.
The JavaScript toolchain is current (see Tests), and that is the point of the fork rather than a detail of it. The plugin still runs on Obsidian 1.7.2 and up, because the floor is set by the API it actually calls rather than by the version it was written against. The TeX side is TeX Live 2023 until 0.3.0 brings TeX Live 2026.
For contrast, the state of the plugin this forks from: its lockfile no longer installed at all, and
its build config listed twenty-one @codemirror packages of which thirteen had been folded into
others years earlier.
npm ci
npm run build # main.js and styles.css, in seconds, with no Docker
The compressed TeX engine is committed, so that is the whole build. CONTRIBUTING.md has the checks CI runs, the rules a change has to keep, how to rebuild the engine, and how releases are cut. Security problems go to SECURITY.md rather than to a public issue. What changed in each release is in CHANGELOG.md.
GPL-3.0-or-later. The plugin embeds a TeX engine derived from drgrice1/tikzjax and drgrice1/web2js, both GPL-3, so the combined work is too.
NOTICE lists every embedded component with its own terms: TeX itself, PGF/TikZ, pgfplots,
circuitikz, chemfig and every other bundled package, and the fonts, which are Basil K. Malyshev's
BaKoMa Type 1 conversions of Computer Modern and the AMS fonts, under the BaKoMa licence. Full
licence texts are in vendor/LICENSES/.
Jim Fowler wrote TikZJax and web2js, which is the hard part and the reason any of this is possible. Glenn Rice maintains the forks this engine is built from. artisticat1 wrote the original Obsidian plugin. Basil K. Malyshev made the BaKoMa fonts every diagram is drawn in. RepentStar contributed the Unicode label bridge, which draws labels in Chinese, Greek, Cyrillic and the other scripts the engine has no fonts for, in pull request #2.