Neovim plugin for real-time bidirectional sync with Overleaf.
Edit your LaTeX projects locally in Neovim. Changes sync to Overleaf on save, and collaborators' edits appear on your disk in real-time.
- Bidirectional sync: local
:wpushes to Overleaf, remote edits write to your files - Real-time collaboration via Operational Transformation (OT)
- Local files on disk — use any tool (LSP, git, grep, etc.)
- Automatic reconnection on network failure
- Statusline integration
- Neovim >= 0.10
- Deno >= 2.0
- denops.vim
{
'mei28/nvim-overleaf',
dependencies = {
'vim-denops/denops.vim',
},
cmd = { 'OverleafInit', 'OverleafSync', 'OverleafStatus' },
opts = {},
}If you don't have Deno:
# macOS
brew install deno
# or curl (Linux/macOS)
curl -fsSL https://deno.land/install.sh | sh- Open overleaf.com in your browser and log in
- Open Developer Tools (F12)
- Go to Application > Cookies >
https://www.overleaf.com - Find
overleaf_session2and copy the value (starts withs%3A)
The cookie expires after about 5 days. Recopy it when it expires.
Copy it from your Overleaf URL:
https://www.overleaf.com/project/64a1b2c3d4e5f6g7h8i9j0
^^^^^^^^^^^^^^^^^^^^^^^^
mkdir my-thesis && cd my-thesis
nvim:OverleafInitYou'll be prompted for the cookie and project ID. The plugin will:
- Connect to Overleaf
- Download all files to the current directory
- Save connection info to
.overleaf/config.json(including cookie)
my-thesis/
├── .overleaf/
│ └── config.json
├── main.tex
├── chapters/
│ └── intro.tex
└── references.bib
:OverleafSync " connect and sync (reads saved cookie from .overleaf/)Then just edit files and :w. Changes push to Overleaf automatically. Collaborators' edits appear in your local files in real-time.
| Command | Description |
|---|---|
:OverleafInit |
First-time setup. Prompts for cookie and project ID. |
:OverleafSync |
Connect and sync. Uses saved cookie from .overleaf/. |
:OverleafOpen |
Pick a document to open in a buffer with real-time OT sync. |
:OverleafStatus |
Show connection state and project info. |
:OverleafMessages |
Show activity log (connection, sync, errors). |
:OverleafDisconnect |
Disconnect from Overleaf. |
:OverleafLogLevel {level} |
Set log verbosity (debug info warn error). |
require('overleaf').setup({
autoread = true, -- silently reload buffers on remote changes (default: true)
})When using Lazy.nvim, pass options via opts:
{
'mei28/nvim-overleaf',
dependencies = { 'vim-denops/denops.vim' },
opts = {
autoread = true,
},
}The plugin exposes a Lua API that returns raw state, so you can format it however you like.
require('overleaf').get_state()
-- => "connected" | "connecting" | "authenticating" | "reconnecting" | "disconnected"
require('overleaf').get_status()
-- => { state = "connected", projectName = "my-thesis", openDocs = 2, syncedFiles = 5, ... }Minimal:
{
function()
return 'OL'
end,
cond = function()
return require('overleaf').get_state() == 'connected'
end,
}With connection state:
{
function()
local state = require('overleaf').get_state()
if state == 'connected' then return 'OL' end
if state == 'reconnecting' then return 'OL …' end
if state == 'connecting' or state == 'authenticating' then return 'OL …' end
return ''
end,
cond = function()
return require('overleaf').get_state() ~= 'disconnected'
end,
}With project name:
{
function()
local status = require('overleaf').get_status()
if status.state == 'connected' then
return 'OL: ' .. (status.projectName or '?')
end
if status.state ~= 'disconnected' then
return 'OL: ' .. status.state
end
return ''
end,
cond = function()
return require('overleaf').get_state() ~= 'disconnected'
end,
}The legacy overleaf#statusline() VimScript function is still available; it returns the raw state string.
Local files Overleaf
+----------+ :w (save) +----------------+
| main.tex | ──────────────────> | |
| | OT operations | real-time |
| (disk) | <────────────────── | collaboration |
+----------+ remote changes +----------------+
| |
| file watcher | Socket.IO v0.9
| Deno.watchFs | WebSocket
v v
+---------------------------------------------+
| Deno (denops) |
| OT engine | Document state | Socket.IO |
+---------------------------------------------+
:wtriggers file watcher -> diff against server state -> OT ops -> send to Overleaf- Remote edits arrive as OT updates -> transform against local state -> write to disk
- All documents stay joined for the duration of the session
- Custom Socket.IO v0.9 client (Overleaf's server requires this protocol version)
- Automatic reconnection with exponential backoff
denops/overleaf/
├── main.ts -- Denops entry point
├── app.ts -- Orchestrator
├── auth/ -- Cookie auth, CSRF extraction
├── document/ -- OT state machine (inflight/pending/version)
├── ot/ -- OT engine (apply, transform, unicode)
├── protocol/ -- Socket.IO v0.9 client, Overleaf events
├── project/ -- File tree, entity ID mapping, .overleaf/ config
├── sync/ -- File watcher, remote applier, echo guard
└── util/ -- EventEmitter, logger, debounce
just ci # fmt + lint + check + test
deno task test # run tests only
deno task check # type check only- www.overleaf.com only (self-hosted Overleaf support planned)
- Requires manual cookie from browser DevTools
- No PDF preview (use VimTeX or an external viewer)
- No LaTeX compilation trigger yet
- Overleaf-Workshop -- VS Code extension
- richwomanbtc/overleaf.nvim -- Neovim plugin (Lua + Node.js)
- AirLatex.vim -- Vim plugin (Python)
MIT