Library / SDK
joemccann/dillinger avatar
joemccann/dillinger

Dillinger: a Next.js Markdown editor with Monaco and OAuth cloud storage

The last Markdown editor, ever.

8,298 stars1,160 forksTypeScriptMIT

At a glance

What is it?
Dillinger is a browser Markdown editor built on Next.js 14 with the Monaco editor, live preview and OAuth integrations for GitHub, Google Drive, Dropbox, OneDrive and Bitbucket. It installs locally in two commands, but every cloud provider needs its own credentials before it does anything useful.
Who is it for?
Adopt Dillinger if you want a self-hosted Markdown editor with Monaco keybindings, live preview and OAuth-backed storage, and you are willing to register OAuth applications for each provider you enable. Do not adopt it if you need a packaged desktop binary, a documented upgrade path, or a project whose release history matches its current code.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 146 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • joemccann/dillinger8,298 ★ · +13
Star history
Repository2026-09-222026-11-14
joemccann/dillinger8.3k8.3k

13 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

What Dillinger solves, and who it is actually for

Dillinger targets a specific annoyance: Markdown editors that are either heavyweight desktop applications or browser toys with no path to real storage. The README frames the gap directly, saying most editors are "bloated", offline-only, or lacking developer ergonomics, and positions Dillinger as the combination of a VS Code-grade editing surface, cloud storage, and a distraction-free view. The intended user is someone who writes Markdown often enough to care about keybindings and scroll sync, but who wants the file to land in GitHub, Google Drive, Dropbox, OneDrive or Bitbucket rather than in a local folder.

The repository is a Next.js 14 application, not a desktop binary. There is no Electron or Tauri entry in the package manifest. That single fact decides most adoption questions: if you want an installable app on a laptop with no server component, this is the wrong shape. If you are comfortable running a Node process, either locally or on a host such as Vercel (vercel.json is present at the top level), the architecture fits.

The editor itself is Monaco, the component that powers VS Code, wired through @monaco-editor/react. Two extra packages, monaco-vim and monaco-emacs, add modal editing. That is an unusual amount of attention to editor ergonomics for a Markdown tool, and it is the strongest argument in the project's favour.

How the editor, preview and storage layers fit together

The stack is visible in package.json. Rendering is markdown-it with a long list of plugins: markdown-it-footnote, markdown-it-deflist, markdown-it-abbr, markdown-it-checkbox, markdown-it-mark, markdown-it-ins, markdown-it-sub, markdown-it-sup, markdown-it-toc and markdown-it-texmath. KaTeX handles math, highlight.js handles code blocks, and DOMPurify sanitises the rendered output. The presence of DOMPurify matters: a Markdown preview that renders raw HTML without sanitising it is an injection surface, and the dependency list shows the project treats it as one. breakdance and turndown point in the other direction, converting HTML back to Markdown.

State lives in Zustand stores, which the repository keeps in a top-level stores/ directory. UI code sits in app/ and components/, reusable logic in lib/ and hooks/. That layout is conventional for the App Router era of Next.js and makes the boundaries easy to trace: a hook reads or writes a store, a component renders it, and lib/ holds the provider clients.

Cloud sync is not a single abstraction. Each provider has its own OAuth flow and its own callback route, which is why the environment template lists five separate client ID and secret pairs. GitHub, Google Drive, OneDrive, Bitbucket and Dropbox are each configured independently, and the README's integration section gives provider-specific setup steps: enable the Drive API, register an Azure app with Files.ReadWrite and User.Read, create a Bitbucket OAuth consumer with repo read/write, create a GitHub OAuth App, and use a Dropbox scoped app with files.metadata.read and files.content.write. Nothing in the README suggests a shared credential layer, so enabling three providers means managing three registrations.

Installing Dillinger locally and writing a first document

The README's quick start is two commands. Node.js 18 or later is required, and npm, yarn, pnpm or bun all work as the package manager. Run these from the repository root, then open the printed URL.

bash
npm install
npm run dev

The dev server listens on port 3000, and the README gives http://localhost:3000 as the address to open. You should see the editor with a preview pane. The package.json also defines a start:test script that binds to 127.0.0.1 on port 3005, which is used by the Playwright suite rather than by hand.

A first document needs no configuration. Type Markdown in the left pane and the preview updates as you write; scroll sync keeps the two panes aligned. Drag and drop accepts .md, .txt and .markdown files. Zen Mode is bound to Cmd/Ctrl + Shift + Z according to the README, and hides the surrounding interface. Export offers Markdown, HTML and PDF.

Cloud storage is where setup begins. Copy the environment template and fill in only the providers you actually want:

bash
cp .env.local.example .env.local

The template lists five blocks of variables. A GitHub integration, for example, needs GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET and GITHUB_REDIRECT_URI set to http://localhost:3000/api/github/callback. Google Drive uses GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET and GOOGLE_REDIRECT_URI pointing at /api/google-drive/callback. OneDrive and Bitbucket follow the same pattern with their own prefixes and callback paths; the template sets DROPBOX_REDIRECT_URI to http://localhost:3000/api/dropbox/. The README does not state what the UI does when a provider is empty or half-configured.

For a production build, the README says to update every OAuth callback to your real domain and then build and start:

bash
npm run build
npm start

If you deploy to a host, every registered callback URL must match the deployed domain, or the provider will reject the redirect.

The release history does not match the code in the repository

This is the first thing to understand before adopting Dillinger. The most recent release listed for the repository is 3.5.1, dated 2016-08-04, described as adding resizable interaction for editor and preview panes. The package.json in the repository declares version 0.2.0. Those two numbers cannot both describe the same codebase, and the README's stack table (Next.js 14, Monaco, Tailwind, Zustand) describes an application that did not exist in 2016.

The practical reading is that the version in package.json tracks the current Next.js rewrite, while the release feed reflects an earlier generation of the project. Anyone who pins a dependency on a published release number will be pinning the old application, not the one documented in the README. The repository has not been archived, and the last push was on 2026-06-21, so the code is recent even though the release tags are not.

The README also states that the test suite contains 294 unit and integration tests plus 39 E2E tests with 98% coverage. Those numbers are asserted in the README; the repository does include vitest.config.ts, vitest.setup.ts and playwright.config.ts, and the test scripts are wired into package.json, so the infrastructure is real. Whether the counts and coverage hold on your checkout is something you can check by running the suite.

Where Dillinger is the wrong tool

The OAuth requirement is the biggest constraint. Every cloud feature depends on credentials you create yourself in five different developer consoles, each with its own permission model. The README's integration notes are a checklist of those consoles rather than a walkthrough, and there is no mention of a token refresh policy, a scope-reduction mode, or what happens when a provider revokes access. If you want cloud sync that works the moment you install the editor, this is not it.

The README does not document rollback, data migration between providers, or conflict resolution when the same file is edited in two places. Auto-save is described only as local persistence, and there is no stated rule for what wins when a locally saved buffer and a cloud copy diverge. For a tool whose whole pitch is storage integration, that is a real gap, and it is the kind of gap that shows up after you have committed real documents to it.

There is also no documented self-hosted deployment path beyond npm run build and npm start. Environment variables are read from .env.local, and the README does not describe a secrets manager, a container image, or a Dockerfile, even though the repository has a vercel.json. If your deployment target is not Node-friendly, you are on your own.

Finally, the editor is a browser application. If your writing happens on a plane, in a locked-down environment, or anywhere you cannot reach the server hosting it, the local persistence layer is the only thing standing between you and a lost draft, and the README does not describe where that data lives or how to export it in bulk.

How Dillinger differs from a desktop Markdown editor

The obvious comparison is a desktop editor such as Obsidian or Typora, and the difference is architectural rather than cosmetic. A desktop editor owns the filesystem: your notes are files in a folder, sync is a separate concern you bolt on, and the application works offline by default. Dillinger inverts that. The filesystem is remote, reached through OAuth APIs, and the application is a web server you run. Offline behaviour is limited to whatever local persistence the app implements, which the README describes in one line.

The trade-off is legibility. With a desktop editor you can open the folder and see your files. With Dillinger, the source of truth is whichever provider you connected, and the app is a client to it. That is a reasonable design if you already live in GitHub or Drive and want an editor that commits there. It is a poor design if you think of your notes as a local corpus.

A second difference is extensibility. Dillinger's Markdown pipeline is assembled from markdown-it plugins listed in package.json, so adding a syntax extension means adding a package and wiring it into the renderer, in TypeScript, inside a Next.js app. Desktop editors typically expose a plugin API instead. Neither approach is better in the abstract, but Dillinger's requires you to maintain a fork.

Licence, maintenance and what an upgrade costs you

Dillinger is MIT licensed, and the LICENSE file is at the repository root. MIT is permissive: you can use, modify and redistribute the code, including commercially, provided the copyright notice and permission notice are retained. That is the extent of what the repository states; questions about your specific obligations are for a lawyer, not for this article.

Upgrade cost is where the version mismatch bites. Because package.json declares 0.2.0 while the release feed stops at 3.5.1 from 2016, there is no obvious upgrade path from a published release to the current code. Treat the repository as the artefact. If you fork it, you inherit Next.js 14.2.35 and React 18 as pinned in package.json, and moving to a newer Next.js major is your migration to plan.

The dependency surface is broad: provider clients such as the dropbox SDK, Monaco, KaTeX, highlight.js, DOMPurify, Puppeteer-core and @sparticuz/chromium for PDF generation, plus a Markdown plugin set. Each of those moves on its own schedule. The verify script exists precisely for this: npm run verify runs lint, typecheck, unit tests and E2E tests in sequence, and it is the cheapest way to find out whether a dependency bump broke the renderer or the editor before you ship it. The last push was on 2026-06-21, so the repository is not stale, but the release tags do not tell you what changed.

Editorial conclusion

Adopt Dillinger if you want a self-hosted Markdown editor with Monaco keybindings, live preview and OAuth-backed storage, and you are willing to register OAuth applications for each provider you enable. Do not adopt it if you need a packaged desktop binary, a documented upgrade path, or a project whose release history matches its current code. Before committing, run npm run verify to see whether the test suite passes on your machine, and check whether the OAuth callback routes you plan to use exist under app/api in the repository. The README's claim of 294 unit tests and 39 E2E tests is a claim about the repository, not a guarantee about your fork.

Frequently asked questions

How do I use dillinger.io locally?

Clone the repository, run npm install and then npm run dev, and open http://localhost:3000. Node.js 18 or later is required, and the README accepts npm, yarn, pnpm or bun as the package manager.

What is Dillinger?

Dillinger is a browser-based Markdown editor built on Next.js 14 with the Monaco editor, live preview and scroll sync. It connects to GitHub, Google Drive, Dropbox, OneDrive and Bitbucket through OAuth and exports to Markdown, HTML or PDF.

How do I use Dillinger?

Type Markdown in the editor pane and the preview updates in real time; drag and drop accepts .md, .txt and .markdown files. Zen Mode is bound to Cmd/Ctrl + Shift + Z, and cloud storage requires filling in the provider credentials in .env.local.

Official sources

  1. joemccann/dillinger on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/joemccann-dillinger.svg)](https://hysenlabs.com/projects/joemccann-dillinger)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/joemccann-dillinger.svg)](https://hysenlabs.com/projects/joemccann-dillinger)

Was this useful?