A lightweight, self-hosted file manager for Cloudflare R2. FlareDrive provides a React and Vite web interface, exposes a WebDAV API through Cloudflare Pages Functions, and stores files in R2. An optional D1 database stores sessions when password login is enabled.
Cloudflare's free tier includes 10 GB of R2 storage and 100,000 Pages Functions invocations per day. View official pricing
简体中文 | English
- Grid and details layouts with sorting and grouping by name, modified time, type, or size
- Search, breadcrumb navigation, and directory URL hashes synchronized with browser Back and Forward
- Desktop marquee selection,
Ctrl/Cmdmulti-select,Shiftrange selection, and context menus - Move, copy, rename, delete, batch download, and copy-link actions
- Recursive folder copy, move, and deletion, including drag-to-folder moves
- Upload one or many files, full folders, or drag-and-drop file and folder trees
- Camera capture and image or video selection on supported mobile devices
- Automatic R2 Multipart Upload for large files, with progress, cancellation, and failure states
- Content-hashed upload thumbnails for images, videos, and PDFs, stored in a hidden internal namespace
- Zoomable image viewing plus native browser audio, video, and PDF viewing
- Online text, Markdown, and HTML editing with save and save-as support
- Preview, split, and edit modes for Markdown; sandboxed iframe previews for HTML
- ZIP entry listing and individual entry downloads
- Local DOCX, PPTX, XLSX, XLS, and CSV rendering inside an isolated sandboxed iframe
- Format-level lazy loading so opening one Office type does not load the other Office renderers
- Standard WebDAV Basic Auth administrator credentials
- Optional single-account password login, D1 sessions, and sign-out-all-devices support
- Optional Cloudflare Turnstile protection for the password login endpoint only
- Scoped WebDAV tokens with read-only
ro, read-writerw, and upload-onlyuppermissions - Optional public read access limited to
GET,HEAD, andPROPFIND
File management and selection
Markdown editing and preview
ZIP file preview
flowchart LR
Browser["Web app<br/>React + Vite"]
WebDAV["/webdav/*<br/>Pages Functions"]
Auth["/api/auth/*<br/>Pages Functions"]
R2["R2 Bucket<br/>BUCKET"]
D1["D1 session database<br/>AUTH_DB, optional"]
Office["Office preview iframe<br/>opaque origin"]
Browser -->|WebDAV requests| WebDAV
WebDAV --> R2
Browser -->|Password login| Auth
Auth --> D1
Browser -. "ArrayBuffer over MessageChannel" .-> Office
The main application downloads an Office file through an authenticated /webdav request, then transfers its ArrayBuffer over a one-time MessageChannel. The iframe uses only sandbox="allow-scripts", without allow-same-origin; its sandbox and CSP jointly block network requests, forms, popups, objects, child frames, and top-level navigation.
Core technologies:
| Layer | Implementation |
|---|---|
| Web frontend | React 19, MUI 9, Vite 8, TypeScript |
| API | Cloudflare Pages Functions / Workers Runtime |
| File storage | Cloudflare R2 |
| Login sessions | Cloudflare D1, optional |
| Document preview | Viewer.js, PDF.js, docx-preview, pptx-preview, SheetJS, Univer |
- A Cloudflare account
- An R2 bucket
- A Git repository connected to Cloudflare Pages, or the Wrangler CLI
Connect your fork or clone to Cloudflare Pages and use these settings:
| Setting | Value |
|---|---|
| Framework preset | None |
| Build command | npm run build |
| Build output directory | build |
| Root directory | Repository root |
Configure at least the R2 binding before deployment:
| Type | Binding name | Required | Purpose |
|---|---|---|---|
| R2 Bucket | BUCKET |
Yes | Stores files and internal thumbnails |
| D1 Database | AUTH_DB |
Password mode only | Stores session hashes and expiration times |
Redeploy after changing bindings or environment variables. public/_routes.json sends only /webdav/* and /api/auth/* through Pages Functions; all other paths are served as static assets.
The default authentication mode is basic. Set:
FLAREDRIVE_AUTH_MODE="basic"
WEBDAV_USERNAME="admin"
WEBDAV_PASSWORD="replace-with-a-strong-password"The browser or WebDAV client displays an HTTP Basic Auth prompt when it first accesses WebDAV.
npm ci
npm run build
npx wrangler pages deploy build --project-name <your-pages-project>| Mode | Web application | WebDAV clients | Primary configuration |
|---|---|---|---|
basic |
Uses the browser HTTP Basic Auth challenge | Administrator credentials or scoped tokens | WEBDAV_USERNAME, WEBDAV_PASSWORD |
password |
Login dialog and D1 session cookie | Still use administrator Basic Auth or scoped tokens | AUTH_DB, FLAREDRIVE_LOGIN_ACCOUNT, FLAREDRIVE_LOGIN_PRIVATE_KEY |
| Public Read | Lists and reads files without authentication | Reads without authentication | WEBDAV_PUBLIC_READ="1" |
Password mode provides one configured web login account. WebDAV clients continue to use WEBDAV_USERNAME, WEBDAV_PASSWORD, or scoped tokens.
- Create a D1 database and bind it as
AUTH_DB. - Apply the session schema:
npx wrangler d1 execute <database-name> --remote --file migrations/0001_auth_sessions.sql- Generate a SHA-256 hash for the login password:
node -e "const crypto=require('crypto'); console.log(crypto.createHash('sha256').update(process.argv[1]).digest('hex'))" "replace-with-a-strong-password"- Generate an ECDH P-256 private key:
node -e "const { webcrypto } = require('crypto'); (async () => { const pair = await webcrypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']); console.log(JSON.stringify(await webcrypto.subtle.exportKey('jwk', pair.privateKey))); })()"- Configure these variables:
FLAREDRIVE_AUTH_MODE="password"
FLAREDRIVE_LOGIN_ACCOUNT='{"username":"admin","password":"<sha256-hex>"}'
FLAREDRIVE_LOGIN_PRIVATE_KEY='<private-jwk-json>'The web login payload is encrypted with an AES-GCM key derived from an ephemeral client ECDH P-256 key. After successful login, the server sets an opaque HttpOnly, SameSite=Lax session cookie; HTTPS requests also receive the Secure attribute. D1 stores only the session token's SHA-256 hash, not the raw token.
Set both values to require Turnstile on the Password login endpoint:
FLAREDRIVE_TURNSTILE_SITE_KEY="<turnstile-site-key>"
FLAREDRIVE_TURNSTILE_SECRET_KEY="<turnstile-secret-key>"Turnstile does not protect WebDAV Basic Auth and does not change scoped-token behavior.
Generate a SHA-256 hash for each raw token secret, then place the hash in WEBDAV_ACCESS_TOKENS:
WEBDAV_ACCESS_TOKENS='[{"username":"phone","password":"<sha256-hex>","access":"rw","includes":["photos/phone/"],"excludes":["photos/phone/private/"]},{"username":"dropbox","password":"<sha256-hex>","access":"up","includes":["uploads/"],"excludes":[]}]'access |
Allowed operations |
|---|---|
ro |
GET, HEAD, and PROPFIND |
rw |
Every supported WebDAV and multipart-upload operation |
up |
File PUT, multipart create/upload/complete, and multipart abort; cannot read, list, create directories, copy, move, or delete existing files |
includes and excludes contain R2 object-key paths, not complete URLs. A scope allows the path itself and its descendants; for example, photos/phone does not match photos/phonebook. Excludes take priority over includes, and COPY or MOVE destinations must also remain inside the allowed scope.
Enter the token username and the raw token secret in a WebDAV client, not the configured SHA-256 hash. A scoped client should connect directly to an allowed prefix, for example:
https://<your-domain>/webdav/photos/phone/
An up token cannot create parent directories, so the target parent must already exist unless the file is uploaded directly to the bucket root.
| Name | Type | Default | Description |
|---|---|---|---|
BUCKET |
R2 binding | None | Required primary file storage |
AUTH_DB |
D1 binding | None | Session database for Password mode |
FLAREDRIVE_AUTH_MODE |
Variable | basic |
basic or password |
WEBDAV_USERNAME |
Secret/variable | None | WebDAV administrator username |
WEBDAV_PASSWORD |
Secret | None | WebDAV administrator password |
WEBDAV_ACCESS_TOKENS |
Secret | None | JSON array of scoped tokens |
WEBDAV_PUBLIC_READ |
Variable | Disabled | Set to 1 to expose GET, HEAD, and PROPFIND publicly |
FLAREDRIVE_LOGIN_ACCOUNT |
Secret | None | Single-account username and password-hash JSON |
FLAREDRIVE_LOGIN_PRIVATE_KEY |
Secret | None | Private ECDH P-256 JWK used by Password login |
FLAREDRIVE_TURNSTILE_SITE_KEY |
Variable | None | Turnstile frontend site key |
FLAREDRIVE_TURNSTILE_SECRET_KEY |
Secret | None | Turnstile server-side secret key |
FLAREDRIVE_SESSION_TTL_SECONDS |
Variable | 86400 |
Standard login lifetime |
FLAREDRIVE_REMEMBER_TTL_SECONDS |
Variable | 604800 |
Keep-signed-in login lifetime |
Store production keys in Cloudflare secrets or protected environment variables. Never commit them to Git.
npm ciCopy the local configuration template:
# Windows PowerShell
Copy-Item .dev.vars.example .dev.vars# macOS / Linux
cp .dev.vars.example .dev.varsEdit .dev.vars. Basic mode does not require D1 initialization. For Password mode, run:
npx wrangler d1 execute AUTH_DB --local --config wrangler.local.jsonc --file migrations/0001_auth_sessions.sqlStart the frontend and backend:
npm run dev| Service | Address | Purpose |
|---|---|---|
| Vite | http://127.0.0.1:3601 |
Frontend, HMR, and /webdav plus /api proxies |
| Wrangler Pages Dev | http://127.0.0.1:3602 |
Pages Functions, local R2, and local D1 |
Open http://127.0.0.1:3601. In Basic mode, the frontend does not embed development credentials in request headers; the browser asks for credentials after /webdav/ returns an authentication challenge.
npm run build
npm run previewnpm run preview serves only the built static frontend at http://127.0.0.1:3600. It does not start Pages Functions.
| Command | Purpose |
|---|---|
npm run dev |
Start Vite and local Pages Functions together |
npm run dev:frontend |
Start only Vite on port 3601 |
npm run dev:functions |
Start only Wrangler Pages Dev on port 3602 |
npm run build |
Type-check and create a production build in build/ |
npm run preview |
Preview the static frontend from build/ |
npm run lint |
Check src/, functions/, and build configuration |
Administrator endpoint:
https://<your-domain>/webdav/
Supported methods are OPTIONS, PROPFIND, MKCOL, HEAD, GET, POST, PUT, COPY, MOVE, and DELETE. POST and some query-parameter forms of PUT and DELETE implement the web application's R2 Multipart Upload flow.
The web application uses a normal PUT below 100,000,000 bytes and switches to its custom multipart flow at that size. Standard WebDAV clients generally use only PUT and do not automatically understand FlareDrive's multipart API. Use the web application for large uploads.
Basic Auth sends Base64-encoded credentials with every request. Base64 is not encryption; always use HTTPS in production.
| Type | Behavior | In-browser size limit |
|---|---|---|
| Images | Zoomable Viewer.js surface | No dedicated limit |
| Audio / video | Native browser player | No dedicated limit |
| Browser viewer in a new window; PDF.js generates thumbnails | No dedicated limit | |
| Text / Markdown / HTML | Preview, edit, save, and save as | 2 MiB |
| ZIP | Entry listing and individual downloads | 30 MiB |
| DOCX / PPTX / XLSX / XLS / CSV | Local parsing and rendering in a sandboxed iframe | Less than 10 MiB |
Office previews target compatibility and quick inspection. Complex fonts, animations, macros, embedded objects, and advanced formulas may not match desktop Office exactly. FlareDrive does not execute Office macros.
- Office and HTML previews run in sandboxed iframes without
allow-same-origin; the Office iframe sandbox and CSP block network access, forms, objects, nested frames, popups, and top-level navigation; DOCX additionally disablesaltChunkHTML and removes links and navigation after rendering; and Markdown rendering skips raw HTML - WebDAV forces HTML, SVG, XHTML, and similar active content to download with attachment disposition,
nosniff, and a sandbox CSP - The internal
_$flaredrive$/namespace is hidden from normal listings and restricted to controlled thumbnail operations - Password mode stores only session token hashes in D1
- Scoped tokens validate source paths, top-level
COPYorMOVEdestinations, and every recursively derived destination
FlareDrive is currently a single-account file manager and does not include a multi-tenant authorization model, operation audit logs, or malware scanning.
index.html Main Vite entry
office-preview.html Isolated Office iframe entry
src/ React web application
src/office-preview/ DOCX, PPTX, and spreadsheet iframe runtime
functions/webdav/ WebDAV Pages Functions and method handlers
functions/api/auth/ Password-session API
functions/auth.ts Login, Turnstile, cookie, and D1 session helpers
migrations/ D1 schema
public/_headers Office iframe CSP and static-asset CORS
public/_routes.json Pages Functions route scope
vite.config.ts Vite, proxy, worker, and preview chunk configuration
- Password mode supports one configured account; it is not a multi-user drive
- Public Read lets any visitor list and read exposed WebDAV content without authentication
- Recursive copy, move, and delete operations on large folders can generate many R2 operations
- This project continues the work from longern/FlareDrive
- The WebDAV implementation originates from abersheeran/r2-webdav


