Skip to content

Repository files navigation

MarkItDown — in your browser

Convert Office files, PDFs, HTML and more to Markdown entirely client-side. Microsoft MarkItDown runs on Python compiled to WebAssembly via Pyodide — your files never leave your device, and there is no server.

Setup

Everything is self-hosted — no CDN is used at runtime. A one-time setup step downloads the Pyodide runtime and all Python wheels into public/.

bun install
bun run fetch-deps   # downloads Pyodide 0.29.4 + wheels into public/ (a few hundred MB)
bun run dev          # http://localhost:4321

bun run build produces a fully static site in dist/. Deploy dist/ to any static host.

fetch-deps prunes the Pyodide distro to just the packages we use (runtime + shared libs + wheels for pandas/lxml/pillow/cryptography/micropip and deps) — ~40 MB instead of ~464 MB, with no single file over Cloudflare's 25 MiB limit. Re-running it is idempotent.

Deploy to Cloudflare (Workers Static Assets, auto-build on push to main)

The site is fully static, so no @astrojs/cloudflare adapter is needed (that adapter is only for SSR). Deployment uses Cloudflare Workers Static Assets via wrangler.jsonc.

One-time: connect the repo in the Cloudflare dashboard

  1. Push this repo to GitHub/GitLab.
  2. Cloudflare dashboard → Workers & Pages → Import a repository → select this repo.
  3. Set:
    • Build command: bun install && bun run cf:build
    • Deploy command: bunx wrangler deploy (auto-detects wrangler.jsonc)
    • Production branch: main
  4. Save. Every push to main now auto-builds and deploys to https://markitdown-browser.<your-subdomain>.workers.dev.

cf:build runs fetch-deps (download Pyodide + wheels → prune) then astro build. Cloudflare's build image includes Bun, Python 3, pip, curl and tar, so this runs in CI. Vendored assets stay out of git (.gitignore) and are regenerated on each build.

Manual deploy (optional)

bun run cf:build        # prune + build → dist/
bunx wrangler login     # one-time auth
bun run deploy          # bunx wrangler deploy

Local preview of the exact production artifact

bun run cf:build
bunx wrangler dev       # serves dist/ as Cloudflare will (default http://localhost:8787)

How it works

  • scripts/fetch-deps.sh vendors the Pyodide distribution into public/pyodide/ (runtime + native wheels: pandas, lxml, Pillow, cryptography, micropip) and the pure-Python wheels into public/py/ (markitdown + transitive deps), emitting public/py/manifest.json.
  • src/workers/markitdown.worker.ts loads Pyodide from /pyodide/, installs the local wheels with micropip (deps:false, explicit local URLs → never hits PyPI), and runs markitdown in a Web Worker so the UI never blocks.
  • markitdown's core magika dependency needs onnxruntime (no WASM wheel), so the worker injects a stub magika module before importing markitdown. markitdown then falls back to extension/MIME-based file detection, using the uploaded filename.
  • src/lib/converter.ts wraps the worker; src/pages/index.astro is the UI.

Supported formats

PDF, DOCX, PPTX, XLSX/XLS, HTML, CSV, JSON, XML, plain text, EPUB.

Audio transcription, YouTube transcripts and AI image OCR are intentionally not supported in local browser mode (they require network services / native libraries).

Offline / PWA

The site is a PWA with a service worker (via @vite-pwa/astro + Workbox). The small app shell is precached; the heavy Pyodide runtime + wheels (/pyodide/*, /py/*) are cached on first use (Workbox CacheFirst). After one successful load the app works fully offline — and reloads no longer re-download the ~46 MB of Python assets (they total ~46 MB in Cache Storage, well within the browser's origin quota).

A small indicator under the header shows "Available offline" once everything is cached, and switches to "Offline — still works" when the browser goes offline.

The service worker registers in production builds only — it is intentionally disabled in astro dev (avoids Vite HMR conflicts and keeps the Playwright suite clean). So in bun run dev assets still re-fetch on reload; to see/verify caching + offline, use a production build:

bun run build && bunx wrangler dev   # serves dist/ with the SW active

Tests

End-to-end Playwright suite that drives the real app (Pyodide + worker + UI) and converts a sample fixture of every supported format, asserting the output Markdown. Pyodide loads once and is shared across cases.

bun run fetch-deps                 # ensure public/ is populated
bunx playwright install chromium   # one-time browser download
bun run test                       # runs tests/conversions.spec.ts
  • Fixtures live in tests/fixtures/ and are committed. Regenerate them with tests/fixtures/generate.py (needs python-docx, openpyxl, xlwt, python-pptx, fpdf2).
  • CI runs the suite on every push/PR via .github/workflows/test.yml (caches the vendored Pyodide distro and the Playwright browser).

About

Convert Office files, PDFs, HTML and more to Markdown in your browser. It runs locally with 100% of privacy documents never leave your device.

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages