Open-source markdown editor & file manager — beautiful, fast, self-hosted.
MD is a self-hosted webapp for writing, managing and exporting Markdown documents. It combines a split-pane editor with a live, typographically polished preview, and supports exporting to a wide range of formats — all from a clean, minimal interface designed for long-form writing in 2026.
It is distributed under the MIT licence and designed to run on your own infrastructure behind your existing security stack (Traefik, CrowdSec, Redis, etc.).
| Category | Details |
|---|---|
| Markdown engine | Full CommonMark + GFM (tables, task lists, strikethrough, autolinks) |
| Extended syntax | Footnotes, definition lists, typographic quotes, emoji :fire: 🔥, frontmatter (YAML) |
| Images | Full image rendering in preview (standalone + linked badges), lazy-loading, external images via HTTPS |
| Code blocks | Syntax highlighting via highlight.js (200+ languages), one-click copy |
| Editor | CodeMirror 6 – markdown syntax highlighting, line numbers, fold gutter, Vim-like keyboard, autocomplete |
| Quick formatting bar | One-tap formatting actions in UI: bold, italic, underline, strike, H1/H2/H3, paragraph, lists (bullet/ordered/tasks), quote, inline code, code block, links |
| Responsive UI | Mobile/tablet optimized layout: adaptive top menu, harmonized button sizes, horizontal quick-toolbar scroll, stacked split view on small screens, sidebar overlay on touch devices |
| Font picker | Per-style font selection (H1–H5, body text). 14 fonts: Lora, Merriweather, Playfair Display, Source Serif 4, Tangerine, Inter, Roboto, Open Sans, Poppins, Exo 2, Ubuntu, Nunito Sans, Raleway, Helvetica — assign any font to any heading level or body text. "Apply everywhere" shortcut for global change. Config persisted in localStorage |
| Typography | Lora serif default, Inter for UI, JetBrains Mono for code |
| Themes | Light / Dark, auto-detects system preference, persists in localStorage |
| View modes | Split (editor + preview), Editor only, Preview only |
| Sidebar | Collapsible file manager panel — toggle via toolbar button, state persisted in localStorage |
| Export | HTML, PDF (via Pandoc + WeasyPrint), DOCX, ODT, EPUB, LaTeX, RST, AsciiDoc, Textile, MediaWiki, Plain text |
| Export without save | Export the current editor content directly — no need to save first |
| Import | Upload .md, .txt, .html files via UI drag-and-drop or API |
| Native browser print with dedicated print stylesheet | |
| File management | Create, read, update, delete, rename, list — stored as flat files on disk |
| REST API | Full JSON API with optional API key auth |
| Cache | Redis-backed rendered HTML cache for fast preview |
| Security | Traefik-first, CrowdSec middleware, HSTS, CSP, no tracking |
Screenshots coming soon — run the app locally to see it live!
| Layer | Technology | Version |
|---|---|---|
| Backend | Go | 1.25 |
| HTTP router | chi | v5 |
| Markdown (server) | goldmark | v1.7 |
| Cache | Redis | 7.4 |
| Frontend | SvelteKit / Svelte | 5 |
| Build tool | Vite | 6 |
| CSS | TailwindCSS | 4 |
| Editor | CodeMirror | 6 |
| Markdown (browser) | marked.js | v15 |
| Syntax highlight | highlight.js | v11 |
| Export engine | Pandoc + WeasyPrint | latest |
| Container | Docker / Alpine | 3.21 |
| Reverse proxy | Traefik | v3 |
| Security | CrowdSec | latest |
infra_proxy network)git clone https://github.com/md-app/md.git
cd md
# Copy and adjust env vars
cp .env.example .env
# Build & start
docker compose up -d --build
# Open in browser
open http://localhost:8080
# 1. Set your env variables
cp .env.example .env
# Edit .env: MD_DOMAIN, MD_API_KEY, REDIS_PASSWORD...
# 2. Deploy
docker compose -f docker-compose.nas.yml up -d --build
# 3. Your app is now available at https://MD_DOMAIN
cd web
npm install
npm run dev # starts on http://localhost:5173 (proxies /api → localhost:8080)
All configuration is done via environment variables.
| Variable | Default | Description |
|---|---|---|
MD_HTTP_ADDR |
:8080 |
HTTP listen address |
MD_STORAGE_PATH |
/data |
Root directory for file storage |
MD_REDIS_URL |
(empty) | Redis URL (disable cache if empty) |
MD_API_KEY |
(empty) | Optional API key (X-API-Key header). Empty = no auth |
MD_APP_URL |
http://localhost:8080 |
Public URL of the app |
MD_CORS_ORIGINS |
* |
Comma-separated allowed CORS origins |
MD_MAX_FILE_SIZE_MB |
10 |
Max upload size in MB |
MD_PANDOC_BINARY |
pandoc |
Path to pandoc binary |
MD_OIDC_ISSUER |
(empty) | OIDC issuer URL (empty = no auth). Enables SSO |
MD_OIDC_CLIENT_ID |
(empty) | OIDC client ID |
MD_OIDC_CLIENT_SECRET |
(empty) | OIDC client secret |
MD_OIDC_REDIRECT_URL |
(empty) | OIDC callback URL (https://…/api/auth/callback) |
MD_OIDC_SESSION_KEY |
(random) | HMAC key for session cookies |
See .env.example for a fully documented sample.
Base URL: https://your-domain/api
| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check |
GET |
/api/files |
List all documents |
POST |
/api/files |
Create a document {name, content, path?} |
GET |
/api/files/:id |
Get document with content |
PUT |
/api/files/:id |
Update document {name, content} |
DELETE |
/api/files/:id |
Delete document |
GET |
/api/files/:id/render |
Get rendered HTML (cached) |
POST |
/api/files/render |
Ad-hoc render {content} |
GET |
/api/files/:id/export/html |
Export as standalone HTML |
POST |
/api/files/:id/export/:format |
Export (pdf, docx, odt, epub, latex, rst, asciidoc, textile, mediawiki, plain) |
POST |
/api/files/import |
Import via multipart form (file field) |
POST |
/api/export/raw/:format |
Export raw content without saving {content, name} |
GET |
/api/export/formats |
List supported export formats |
GET |
/api/templates |
List 8 built-in templates |
GET |
/api/templates/:id |
Get template with full content |
GET |
/api/search?q=…&path=… |
Full-text search across documents |
GET |
/api/files/:id/versions |
List version history |
GET |
/api/files/:id/versions/:vid |
Get version content |
POST |
/api/files/:id/versions/:vid/restore |
Restore a version |
GET |
/api/files/:id/events |
SSE stream for collaborative editing |
POST |
/api/files/:id/broadcast |
Broadcast edit to collaborators |
GET |
/api/webhooks |
List webhooks |
POST |
/api/webhooks |
Create webhook {url, events[], secret} |
PUT |
/api/webhooks/:id |
Update webhook |
DELETE |
/api/webhooks/:id |
Delete webhook |
GET |
/api/plugins |
List loaded plugins |
GET |
/api/auth/login |
OIDC login redirect (when configured) |
GET |
/api/auth/callback |
OIDC callback |
GET |
/api/auth/me |
Current user info |
GET |
/api/auth/logout |
Logout |
Authentication (when MD_API_KEY is set):
X-API-Key: your-key
or ?api_key=your-key query param.
| Format | File | Engine |
|---|---|---|
| HTML | .html |
goldmark (Go, built-in) |
.pdf |
Pandoc + WeasyPrint | |
| Word | .docx |
Pandoc |
| OpenDocument | .odt |
Pandoc |
| EPUB | .epub |
Pandoc |
| LaTeX | .tex |
Pandoc |
| reStructuredText | .rst |
Pandoc |
| AsciiDoc | .adoc |
Pandoc |
| Textile | .textile |
Pandoc |
| MediaWiki | .wiki |
Pandoc |
| Plain text | .txt |
Pandoc |
Note: PDF and non-HTML formats require
pandoc(andweasyprintfor PDF) to be present in the runtime environment. These are pre-installed in the production Docker image.
| Shortcut | Action |
|---|---|
Ctrl/⌘ + S |
Save document |
Ctrl/⌘ + B |
Bold selection |
Ctrl/⌘ + I |
Italic selection |
Ctrl/⌘ + K |
Insert link |
Ctrl/⌘ + Z / Ctrl/⌘ + Y |
Undo / Redo |
Ctrl/⌘ + F |
Search in editor |
Tab |
Indent |
Shift + Tab |
Dedent |
Two workflows are available in .github/workflows/:
ci.yml: quality gate on push/PR (go vet, Go build/tests, Svelte type-check, frontend build, Docker build)cd-prod.yml: validation + production deploy over SSH on main and manual triggerTo enable automated production deployment, configure these GitHub secrets:
MD_PROD_SSH_HOSTMD_PROD_SSH_USERMD_PROD_SSH_KEYMD_PROD_SSH_PORT (optional, default 22)MD_PROD_APP_DIR (optional, default /srv/apps/md)Deploy job behavior:
main on the production hostdocker compose -f docker-compose.nas.yml up -d --build --remove-orphansInternet → Traefik (TLS termination)
│
├── CrowdSec Bouncer (rate limiting, IP reputation)
│
└── MD (Go API + SvelteKit SPA)
│
└── Redis (cache, localhost only)
X-Frame-Options, X-Content-Type-Options, CSP, Referrer-Policymd:md)no-new-privileges security option enabledapps/md/ ← (rename to "md" in production)
├── cmd/
│ └── server/
│ └── main.go ← Entry point
├── internal/
│ ├── api/
│ │ ├── router.go ← chi router + middleware wiring
│ │ ├── files.go ← CRUD handlers + markdown render
│ │ ├── export.go ← Multi-format export via Pandoc
│ │ ├── health.go ← Health + JSON helpers
│ │ └── middleware.go ← Logging, API key, security headers
│ ├── config/
│ │ └── config.go ← Env-based configuration
│ ├── storage/
│ │ └── storage.go ← File system CRUD (JSON meta + .md content)
│ └── cache/
│ └── redis.go ← Redis client wrapper
├── web/ ← SvelteKit 5 frontend
│ ├── src/
│ │ ├── App.svelte ← Root component, layout
│ │ ├── main.ts ← Entry point
│ │ ├── app.css ← Global styles + prose + CodeMirror overrides
│ │ └── lib/
│ │ ├── api.ts ← Typed API client
│ │ ├── stores/
│ │ │ └── files.ts ← Svelte stores + async actions
│ │ ├── components/
│ │ │ ├── Sidebar.svelte ← Collapsible file list + search + import
│ │ │ ├── Toolbar.svelte ← Title, sidebar toggle, view toggle, save, export
│ │ │ ├── Editor.svelte ← CodeMirror 6 editor
│ │ │ ├── Preview.svelte ← marked.js live preview (images, links, badges)
│ │ │ ├── ExportModal.svelte ← Format picker + download
│ │ │ ├── FontPicker.svelte ← Per-style font picker (H1–H5, body)
│ │ │ └── Particles.svelte ← Canvas particle animation
│ ├── package.json
│ ├── vite.config.ts
│ ├── svelte.config.js
│ └── tsconfig.json
├── pandoc/
│ └── print.css ← PDF/print stylesheet
├── Dockerfile.app ← Multi-stage: Node → Go → Alpine+Pandoc
├── docker-compose.yml ← Local dev
├── docker-compose.nas.yml ← NAS Synology + Traefik + CrowdSec
├── docker-compose.cloud.yml ← Cloud/VPS + Traefik
├── go.mod
├── .env.example
├── .gitignore
└── README.md
# Install dependencies
go mod tidy
# Run (hot reload with Air)
go run ./cmd/server
# With custom config
MD_HTTP_ADDR=:9090 MD_STORAGE_PATH=./dev-data go run ./cmd/server
cd web
npm install
npm run dev # http://localhost:5173 → proxies /api to localhost:8080
docker build \
-f Dockerfile.app \
--build-arg VERSION=1.0.0 \
--build-arg GIT_SHA=$(git rev-parse --short HEAD) \
--build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-t md/app:1.0.0 .
# Go tests
go test -v ./internal/...
# Frontend
cd web && npm test
$$…$$) and inline ($…$) LaTeX equation renderingStorageBackend interface with local + S3 adaptersContributions are welcome! Please:
git checkout -b feat/your-feature)Please run go vet ./... and cd web && npm run typecheck before submitting.
MIT © 2026 MD Contributors
MD, a Cybergraphe product.