A LaTeX editor that runs entirely on your own machine.
Overleaf's workflow. Obsidian's interface. None of the cloud.
Two commands. That is the whole setup.
npm install # installs dependencies and builds the interface
npm start # opens OpenLeaf in its own windowYou also need a LaTeX engine, which is one more command if you do not already have one:
brew install texlive # macOS, recommended
sudo apt install texlive-full latexmk # Debian or UbuntuPrefer a single self-contained binary that fetches packages on demand? Install
Tectonic instead with
brew install tectonic. OpenLeaf detects whatever you have and picks the
fastest one.
Other ways to install
# Install the command globally, then run it from anywhere
npm install -g openleaf
openleaf
# Run once without installing
npx openleaf
# From a clone, without npm scripts
git clone https://github.com/vicky157/openleaf.git
cd openleaf && npm install && ./bin/openleafCommand line options
openleaf [options]
--no-open start the server without opening a window
--browser use your default browser instead of an app window
--port <n> listen on a specific port (default 4173)
--projects <dir> use a different projects folder
--version print the version
--help show this message
Environment variables: OPENLEAF_HOME sets the projects folder, PORT sets the
port.
That opens a dedicated window with no tabs and no address bar, so it reads as an application rather than a web page. On first run OpenLeaf creates a starter project so there is something on screen immediately.
Overleaf is good software. It also means your unpublished work sits on someone else's servers, your compile speed depends on your connection and their queue, and the features you want most are behind a subscription.
| Overleaf | OpenLeaf | |
|---|---|---|
| Where your files live | Their servers | Your disk |
| Where compiling happens | Their servers | Your CPU |
| Works on a plane | No | Yes |
| Compile time limit | Yes, on the free plan | None |
| Git integration | Paid | Your files are already local, so use git directly |
| Version history | Paid | Included |
| Document graph | No | Included |
| Reference verification | No | Included |
| Cost | Subscription | Free |
| Source available | Partly | Entirely, MIT |
Being honest about it: real-time collaboration. Overleaf lets several people type in the same document at once, and OpenLeaf does not. If that is central to how you work, keep using Overleaf for those documents. See Collaboration for what does work today.
This is the reason the project exists, so it is worth being precise rather than just claiming "private".
Your documents never leave your machine. There is no account, no sync, no telemetry, no crash reporting, and no analytics. Compiling happens in a local process. The only network requests OpenLeaf ever makes are the ones you trigger by searching for a reference, and those go to public scholarly databases (DBLP, Crossref, OpenAlex, arXiv) carrying nothing but your search terms. Work offline and everything except reference search behaves normally.
The server is bound to localhost. It listens on 127.0.0.1 only, so nothing
on your network can reach it. Other machines cannot connect even if they know
the port.
Cross-origin requests are refused. A page you have open in another tab
cannot talk to OpenLeaf, because any request carrying a foreign Origin header
is rejected outright. This matters: the server exposes part of your filesystem,
and without that check a malicious web page could read it.
Path traversal is contained. Every path from the client is resolved to an
absolute path and checked to still be inside the project folder before it is
touched, so ../../.ssh/id_rsa fails. Symlinks are re-checked after resolution,
so a link pointing outside the project is refused too. Imported archives get the
same treatment, which stops the classic zip-slip attack where an entry named
../../../etc/cron.d/x escapes the extraction directory. The test suite asserts
all of this.
Nothing is executed on your behalf. OpenLeaf runs the LaTeX engine you installed and nothing else. It does not evaluate code from your documents, it does not fetch and run scripts, and it has no plugin system that could.
Writes are atomic. Files are written to a temporary path and renamed into place, so a crash mid-save cannot leave you with a truncated chapter, and the compiler can never read a half-written file.
Your files stay ordinary files. A project is a folder of .tex and .bib
files, with no database and no proprietary container. Point git at it, back it
up with Time Machine, open it in Vim. If you stop using OpenLeaf tomorrow, your
work is exactly where it was.
- A document graph that shows how your project actually fits together, and which parts are broken. This is the feature Overleaf has no equivalent of.
- Dark and light themes with frosted, translucent panels, or follow the system
- Six accent colours, and a switch to turn transparency off entirely
- Focus mode that dims everything but the line you are editing
- Side navigation rail with eight panes, one keystroke each
- Collapsible file tree with drag-and-drop, tabbed editing, resizable panes
⌘Kcommand palette over every command, file, section and snippet- Document outline for the current file or the whole project
- An Insert panel of ready-made figures, tables, equations and environments
- Right-click menus everywhere, and undo on the things that would hurt
- Compiles as you type, or
⌘↩to build now - Live PDF preview that keeps your place across rebuilds
- Double-click the PDF to jump to the source line that produced it, across
\inputfiles.⌘Jgoes the other way - Smooth zoom that stays responsive, then re-renders sharp
- Clickable citations and cross-references in the PDF: click
[Knuth, 1984]and the matching.bibentry opens at its exact line - Errors explained in plain language, attributed to the file that actually caused them, with gutter markers and line highlighting
- Version history with restore, kept locally
- Autocomplete over ~180 commands plus your own labels and citation keys
- Reference search and verification across four scholarly databases
- Import an Overleaf export, a folder, or a
.zipby dropping it on the window - Images and PDFs open in tabs of their own
- Word count that ignores macros and maths
Press ⌘3, or click the node count in the status bar.
A LaTeX project is already a dense web of links, spread across four different
syntaxes: \input, \ref, \cite and \includegraphics. OpenLeaf reads all
of them and draws one graph.
Node size follows word count, so the shape of your document is visible at a glance. Click any node to open what it stands for. Drag one to pin it, and double-click to release it.
It doubles as a project health check. The things a graph makes obvious are exactly the things that are hard to notice while writing:
| What it finds | Why it matters |
|---|---|
\ref with no matching \label |
Renders as a bare ?? in the PDF |
\cite with no .bib entry |
Renders as [?] |
\includegraphics pointing at nothing |
Breaks the build |
| Files not reachable from your main document | A chapter you forgot to \input |
| Labels never referenced | Usually harmless, sometimes a typo |
| Bibliography entries never cited | Clutter, and a hint you cited the wrong key |
LaTeX reports the first two only as warnings buried in the log, and the middle one only when the build already failed. Seeing them listed is the difference between finding them now and finding them in a submitted PDF.
| Writing | |
|---|---|
⌘S / ⌘↩ |
Save and compile |
⌘B / ⌘I |
Bold, italic |
⌘M |
Wrap in maths |
⌘/ |
Comment out |
⌘Space |
Autocomplete |
⌘F |
Find in file |
⌘N |
New file |
⌘W |
Close tab |
F2 |
Rename |
| Preview | |
|---|---|
| Double-click | Jump to that line of source |
⌘-click |
Same thing |
⌘J |
Show the cursor's position in the PDF |
⌘-scroll |
Zoom |
⌘+ / ⌘- / ⌘0 |
Zoom in, out, fit |
| Getting around | |
|---|---|
⌘K |
Command palette |
⌘⇧F |
Find a reference |
⌘1 to ⌘8 |
Sidebar panes |
⌘⌥1 to ⌘⌥3 |
Editor, split, preview |
⌘\ |
Toggle sidebar |
⌘, |
Settings |
? |
This list |
- In Overleaf: Menu → Download → Source to get a
.zip - Drag it onto the OpenLeaf window
That is all. The redundant top-level folder is stripped so nothing ends up
nested a level too deep, .git and stale build artifacts are skipped, the root
document is detected, and it compiles immediately.
Folders work the same way, either dropped or through Import in the Projects
pane. Dropping loose files instead adds them to the current project, with images
routed to figures/ so \includegraphics paths stay tidy.
⌘⇧F searches DBLP, Crossref, OpenAlex and arXiv at once. Type
a title, an author, a DOI, an arXiv id, or paste a whole BibTeX entry from a
publisher page; the box works out which it is.
Each result shows venue, year, entry type, citation count, and which sources
agree on it, because a record corroborated by several databases is far more
trustworthy than one seen once. Pressing ↩ fetches the authoritative BibTeX
(DBLP's own export, or the DOI registrar's via content negotiation, never a
guess reconstructed from search results), writes it to your .bib with a
generated surnameYEARword key, and drops \cite{key} at the cursor.
If none of the four has the paper, footer links open the same query in Google, Google Scholar and Semantic Scholar. Paste what you find back into the box.
The ✓ button in the References pane checks every entry against the published record, field by field: author list, title, venue, year. Entries with a DOI or arXiv id are matched by identifier, the rest by title above a similarity threshold.
The most common real error it catches is a truncated author list: an entry with two authors for an eight-author paper. Click any flagged entry for a red-and-green diff, tick the fixes you want, and only those fields are rewritten.
Wrong bibliographic "corrections" are worse than none, so:
- It never verifies against a different paper. A candidate must clear a title-similarity bar, relaxed only when the first author's surname also matches, so short titles like The TeXbook still resolve.
- It never downgrades a real venue to a preprint. If only an arXiv record exists for something you cite as a CVPR paper, the venue is reported as unconfirmed rather than "corrected".
- It ignores duplicate DOI registrations. Some registrars mint a DOI per mirror of a famous paper, carrying the right title with a wrong year. Those are rejected outright.
- "Not found" is a real answer. When nothing trustworthy matches, the entry is marked unverified with a reason and no corrections are offered.
Why not scrape Google Scholar?
Google Web Search has no public API, and Google Scholar has none either and serves CAPTCHAs to automated clients, so scraping it would be unreliable and against its terms. The four databases used here are the ones Scholar itself aggregates: free, no API key, and they return structured records plus real BibTeX.
Every save records a compressed snapshot, so you can go back to any earlier state of a file. Open the History pane, click a revision to preview it, or Restore to put it back. Restoring is itself undoable.
Identical content is never stored twice, and rapid saves collapse into one entry,
so the timeline shows real revisions rather than keystrokes. Snapshots live in
~/.cache/openleaf/history/, outside your project folder, so they never end up
in a git commit or a shared directory.
Measured on an M-series Mac with the starter article, in steady state:
| Engine | No-change rebuild | After a text edit | Notes |
|---|---|---|---|
| latexmk + pdfTeX | 0.09s | 0.72s | Default. Tracks dependencies |
| pdfTeX alone | 0.90s | Single pass, orchestrated here | |
| latexmk + XeTeX | 1.4s | Unicode and system fonts | |
| latexmk + LuaTeX | 1.6s | Lua scripting, fontspec |
|
| Tectonic | 3.3s | Reruns TeX twice plus BibTeX every time |
Tectonic is convenient, needing no TeX distribution, but it cannot skip work
because it keeps no dependency database. latexmk reads the .fls list pdfTeX
emits, so an edit touching no citations or cross-references costs one pass, and
an edit changing nothing costs almost nothing.
Change engine in Settings → Build, or by clicking the engine name in the status bar. The choice is saved per project.
Real-time co-editing is not implemented. What works well today:
Git. Your project is a plain folder, so this needs nothing from OpenLeaf:
cd ~/path/to/projects/My\ Paper
git init && git add . && git commit -m "First draft"Add a .gitignore with *.aux, *.log, *.pdf if you like, though OpenLeaf
already keeps build artifacts out of your project folder.
A shared folder. Point OPENLEAF_HOME at a synced directory and take turns.
Writes are atomic, so a sync landing mid-save cannot corrupt a file, but two
people editing the same file at the same time will produce a sync conflict.
Export. Send a .zip of the folder, or the compiled PDF.
Proper collaboration is the most-requested thing this project does not do, and contributions are welcome. The realistic design is CRDT-based peer-to-peer editing over WebRTC, which would keep the no-server promise intact.
No frontend framework, no bundler config beyond a 60-line esbuild script, and a server with no runtime dependencies at all.
bin/openleaf launcher: builds if needed, starts the server, opens a window
build.js esbuild bundle
server/
index.js REST API, server-sent events, static files
graph.js document graph: parses \input, \ref, \cite, \includegraphics
compile.js engine driver, compile queue, build-directory mirroring
engines.js engine registry, detection, rerun heuristics
synctex.js SyncTeX parser, forward and inverse
logparse.js LaTeX log extraction and file attribution
bibsearch.js multi-source scholarly search
bibverify.js field-by-field verification against published records
bibparse.js BibTeX parser: macros, accents, concatenation
importer.js folder and .zip import, ZIP reader
history.js compressed local snapshots
settings.js global and per-project settings
paths.js path-traversal containment
sample.js project templates
web/
index.html app shell
styles.css the interface
assets/ logo and icons
src/
app.js application logic
editor.js CodeMirror 6 setup
pdfview.js pdf.js viewer, smooth zoom, lazy page rendering
graphview.js force-directed graph on a canvas
bibui.js reference search and verification
latex-lang.js LaTeX syntax highlighting
latex-complete.js autocomplete
snippets.js insertable blocks
ui.js palette, dialogs, toasts, settings
test/run.js end-to-end suite: real server, real browser, real compiles
Where things go on disk:
| Path | Contents |
|---|---|
./projects/ |
your documents (override with OPENLEAF_HOME) |
~/.cache/openleaf/build/ |
.aux, .log and friends, out of your way |
~/.cache/openleaf/history/ |
version snapshots |
~/.config/openleaf/settings.json |
global settings |
<project>/.openleaf.json |
that project's main file and engine |
A few decisions that are not obvious from the code:
Compiles are serialised per project. Engines write .aux files into a shared
build directory, so two concurrent runs would corrupt each other. A request
arriving mid-compile sets a rerun flag instead of starting a second engine, and
every waiter resolves with the result of that follow-up run, so you never see a
PDF that predates your last keystroke.
Sources are mirrored, not compiled in place. Files are copied into the build
directory only when changed, which keeps .aux files valid so reruns stay fast
while leaving your project folder clean.
Zoom is a transform first and a re-render second. Re-rasterising every page on every wheel tick makes pinch-zoom lurch. A CSS transform carries the gesture at display rate, and the sharp render lands 90ms after it settles.
Only visible pages are rasterised. A 300-page thesis would otherwise spend a long time drawing pages nobody is looking at. Canvases far from the viewport are released, so memory stays bounded.
Inverse SyncTeX resolves in two stages. A paragraph's enclosing box is
tagged with the line where the paragraph broke, while the zero-width records
inside carry the true per-word tags. A click first locates the line band, then
picks the nearest leaf within it. Otherwise every click in a paragraph would
resolve to its last line, and text from \input files would never resolve.
Engine path quirks are normalised. latexmk records SyncTeX inputs as
/abs/build/./sections/results.tex, with an embedded /./. Without collapsing
that, subdirectory paths degrade to a bare filename and reverse-sync opens the
wrong file. Every engine is asserted to round-trip a subfile path in the tests.
Citation links come from the PDF, not from guesswork. hyperref emits named
destinations (cite.knuth1984, section.2), so a click reads the exact BibTeX
key from the annotation rather than inferring it from nearby glyphs.
"Nothing to do" is a success. latexmk leaves the PDF untouched when no input changed, so an unchanged mtime cannot mean failure. An existing PDF with no errors counts as up to date.
Deletes offer undo instead of demanding confirmation. Retyping a filename to confirm is friction on every delete, including the ones you meant. The content is held in memory and the toast offers to put it back.
npm testBoots the real server in a temporary directory, drives the real interface in
headless Chrome over the DevTools protocol, and runs real compiles. Covers the
API, security boundaries (path traversal, zip-slip, cross-origin), SyncTeX in
both directions on every installed engine, PDF rasterisation, citation links,
the document graph, version history, project import, BibTeX parsing, reference
search and verification, autocomplete, and the command palette. Screenshots land
in test/screenshots/.
"No LaTeX engine found"
Install one: brew install texlive on macOS, sudo apt install texlive-full latexmk on Debian or Ubuntu, or brew install tectonic for a single binary.
Then click the engine name in the status bar to confirm it was detected.
The window did not open
The server still started. Open http://127.0.0.1:4173 yourself, or run
openleaf --browser to use your default browser. --port 5000 if something
else holds the port, though OpenLeaf already tries the next free one.
Double-click in the PDF does nothing
SyncTeX data has to exist, which means compiling at least once since the last
edit. If it still does not work, check that the engine is producing a
.synctex.gz; some minimal TeX installations omit SyncTeX support.
A package is missing
OpenLeaf runs your TeX installation, so install the package the usual way:
tlmgr install <name>, or use texlive-full. Tectonic downloads packages on
demand instead, so switching to it can be quicker than chasing one package.
Where are my files?
In ./projects/ next to the checkout, or ~/OpenLeaf for a global install. The
Projects pane shows the exact path, and OPENLEAF_HOME overrides it. They are
ordinary folders; open them in Finder or a terminal.
Bug reports, features and pull requests are all welcome. See CONTRIBUTING.md for how to get set up and what the codebase expects. Good first issues are tagged in the tracker; real-time collaboration is the biggest open problem if you want something substantial.
MIT. See LICENSE. Use it, change it, ship it.
Built for people who would rather their unpublished work stayed on their own computer.