R2-Explorer is a Google Drive-like interface for Cloudflare R2 storage buckets, built as a serverless application on Cloudflare Workers. It provides an intuitive web-based UI for managing R2 object storage with features like drag-and-drop uploads, file previews, folder management, and email integration.
Key Characteristics:
- Serverless architecture using Cloudflare Workers
- Monorepo structure managed with pnpm workspaces
- TypeScript-based backend with Hono framework
- Vue 3 + Quasar frontend
- Published as an npm package for easy deployment
- Zero dependencies on external servers (fully self-hosted on Cloudflare)
R2-Explorer/
├── packages/
│ ├── worker/ # Backend API (Cloudflare Worker)
│ ├── dashboard/ # Frontend UI (Vue 3 + Quasar)
│ ├── docs/ # Documentation site
│ └── github-action/ # GitHub Action for deployment
├── template/ # Starter template for users
├── package.json # Root workspace config
└── pnpm-workspace.yaml # Monorepo configuration
- Technology: TypeScript, Hono, Chanfana (OpenAPI framework)
- Entry Point:
src/index.ts - Purpose: REST API that interfaces with Cloudflare R2 buckets
- Key Features:
- R2 bucket operations (CRUD)
- Multipart upload support
- Public sharing links with password protection and expiration
- Email routing integration
- Basic auth and Cloudflare Access authentication
- Read-only mode support
Main Export:
export function R2Explorer(config?: R2ExplorerConfig)API Routes:
/api/server/config- Server configuration/api/buckets/:bucket- List objects/api/buckets/:bucket/:key- Get/Head/Post object/api/buckets/:bucket/move- Move objects/api/buckets/:bucket/folder- Create folders/api/buckets/:bucket/upload- Upload files/api/buckets/:bucket/multipart/*- Multipart upload endpoints/api/buckets/:bucket/delete- Delete objects/api/buckets/:bucket/:key/share- Create public share link/api/buckets/:bucket/shares- List all share links/api/buckets/:bucket/share/:shareId- Delete/revoke share link/share/:shareId- Public access to shared file (no auth required)/api/emails/send- Send emails
- Technology: Vue 3, Quasar Framework, Vite
- Purpose: Single-page application for file management UI
- Key Features:
- Drag-and-drop file uploads
- File preview (PDF, images, text, markdown, CSV)
- Folder navigation
- Context menus for file operations
- Public share link creation and management
- Email viewer for attachments
Build Output:
- Compiled to
dist/spa/directory - Bundled with worker package as static assets
- Served via Cloudflare Workers Assets binding
- Purpose: User-facing starter template
- Contents:
- Minimal
src/index.tsthat imports and configuresr2-explorerpackage wrangler.tomlfor Cloudflare Workers configuration- Example configuration options
- Minimal
type R2ExplorerConfig = {
readonly?: boolean; // Default: true (prevents write operations)
cors?: boolean; // Enable CORS for API endpoints
cfAccessTeamName?: string; // Cloudflare Access team name
dashboardUrl?: string; // Custom dashboard URL
emailRouting?: { // Email routing configuration
targetBucket: string;
} | false;
showHiddenFiles?: boolean; // Show files starting with .
basicAuth?: BasicAuth | BasicAuth[]; // Basic authentication
};
type BasicAuthType = {
username: string;
password: string;
};[[r2_buckets]]
binding = "BUCKET_NAME" # Access via env.BUCKET_NAME in code
bucket_name = "actual-bucket-name"
# Assets binding (automatic)
assets = {
directory = "node_modules/r2-explorer/dashboard",
binding = "ASSETS",
html_handling = "auto-trailing-slash",
not_found_handling = "single-page-application"
}- Node.js (v16+)
- pnpm package manager
- Cloudflare account with R2 enabled
- Wrangler CLI (included as dev dependency)
# Install dependencies
pnpm install
# Build everything
pnpm build
# Build individual packages
pnpm build-dashboard # Builds Vue SPA
pnpm build-worker # Builds TypeScript + bundles dashboard# Lint code (uses Biome)
pnpm lint
# Build dashboard
pnpm build-dashboard
# Build worker
pnpm build-worker
# Deploy dashboard to Cloudflare Pages
pnpm deploy-dashboard # Production
pnpm deploy-dashboard-dev # Development
# Package for npm
pnpm package
# Publish to npm
pnpm publish-npm# Run worker tests
cd packages/worker
pnpm testTests use Vitest with @cloudflare/vitest-pool-workers for Worker runtime testing.
- Uses pnpm workspaces for package management
- Packages are linked locally during development
- Template is excluded from workspace (standalone user template)
- Dashboard Build: Quasar builds Vue app to
packages/dashboard/dist/spa/ - Worker Build:
- tsup compiles TypeScript to CJS/ESM
- Copies dashboard
dist/spa/topackages/worker/dashboard/ - Copies README.md and LICENSE
- Creates npm package with all assets
Users install the npm package and deploy via Wrangler:
import { R2Explorer } from "r2-explorer";
export default R2Explorer({ /* config */ });Two authentication methods supported:
- Basic Auth: Hono's
basicAuthmiddleware with custom verifier - Cloudflare Access:
@hono/cloudflare-accessmiddleware
Middleware chain: CORS → ReadOnly → Authentication → API Routes
- Default mode for safety
- Middleware blocks write operations (POST, PUT, DELETE)
- Can be disabled via config for full access
- Cloudflare Email Routing integration
receiveEmailhandler processes incoming emails- Emails stored in configured R2 bucket
- Parsed with
postal-mimelibrary - Dashboard displays email with attachments
- Tool: Biome (replaces ESLint/Prettier)
- Config:
biome.jsonat root - Auto-fix:
npx @biomejs/biome check --write
- Strict mode enabled
- Types defined in
packages/worker/src/types.d.ts - OpenAPI schema generation via Chanfana
- Composition API preferred
- Quasar components for UI
- Component structure:
packages/dashboard/src/ ├── components/ # Reusable components ├── pages/ # Route pages ├── layouts/ # Page layouts ├── stores/ # Pinia stores └── router/ # Vue Router config
- Create endpoint class in
packages/worker/src/modules/ - Extend
OpenAPIRoutefrom Chanfana - Define schema with Zod
- Implement handler logic
- Register in
packages/worker/src/index.ts
Example:
import { OpenAPIRoute } from "chanfana";
import { z } from "zod";
export class MyEndpoint extends OpenAPIRoute {
schema = {
request: {
params: z.object({
bucket: z.string(),
}),
},
responses: {
"200": { description: "Success" },
},
};
async handle(c) {
const { bucket } = await this.getValidatedData<typeof this.schema>();
// Implementation
return c.json({ success: true });
}
}- Locate relevant component in
packages/dashboard/src/ - Edit Vue component (template/script/style sections)
- Use Quasar components for consistency
- Update API calls in
appUtils.jsif needed - Rebuild dashboard:
pnpm build-dashboard
-
Worker:
- Add tests in
packages/worker/tests/ - Run:
cd packages/worker && pnpm test
- Add tests in
-
Local Development:
- Use
wrangler devin template directory - Set up local R2 bindings or use remote
- Use
-
Integration:
- Build both packages:
pnpm build - Deploy to Cloudflare Workers for testing
- Build both packages:
- Update version in
packages/worker/package.json - Build:
pnpm build - Package:
pnpm package - Publish:
pnpm publish-npm - Update template dependency version
| File | Purpose |
|---|---|
packages/worker/src/index.ts |
Main worker entry point, route definitions |
packages/worker/src/types.d.ts |
TypeScript type definitions |
packages/worker/src/modules/buckets/createShareLink.ts |
Create public share links |
packages/worker/src/modules/buckets/getShareLink.ts |
Public share link access |
packages/worker/src/modules/buckets/listShares.ts |
List all share links |
packages/worker/src/modules/buckets/deleteShareLink.ts |
Revoke share links |
packages/dashboard/src/appUtils.js |
API client utilities |
packages/dashboard/src/pages/files/FilesFolderPage.vue |
Main file browser UI |
packages/dashboard/src/pages/files/FileContextMenu.vue |
File context menu with share options |
packages/dashboard/src/components/files/ShareFile.vue |
Share link creation and management UI |
packages/dashboard/src/components/utils/DragAndDrop.vue |
Drag-drop upload |
template/src/index.ts |
User template entry point |
template/wrangler.toml |
Cloudflare Workers config |
package.json (root) |
Monorepo scripts |
pnpm-workspace.yaml |
Workspace configuration |
biome.json |
Linting configuration |
- Check Wrangler logs:
wrangler tail - Verify R2 bucket bindings in
wrangler.toml - Test auth configuration
- Check CORS settings if using external API calls
- Inspect browser console for API errors
- Verify API endpoint responses
- Check Quasar dev mode:
cd packages/dashboard && pnpm dev - Review
appUtils.jsfor API call logic
- Clear
node_modulesand reinstall:rm -rf node_modules && pnpm install - Clean build outputs:
rm -rf packages/*/dist - Verify pnpm version compatibility
- Check tsup/vite build logs
- Authentication: Always enable either Basic Auth or Cloudflare Access in production
- Read-Only Mode: Keep enabled unless write access is required
- CORS: Only enable if external API access is needed
- Secrets: Use Wrangler secrets for sensitive config (not in code)
- R2 Bucket Access: Ensure Worker only has necessary R2 permissions
- Share Links:
- Share metadata stored in
.r2-explorer/sharable-links/prefix - Passwords hashed with SHA-256
- Expiration checked on every access
- Public
/share/:shareIdendpoint bypasses authentication - Consider setting download limits for sensitive files
- Share metadata stored in
- Documentation: https://r2explorer.com
- Demo: https://demo.r2explorer.com
- Repository: https://github.com/G4brym/R2-Explorer
- Cloudflare R2 Docs: https://developers.cloudflare.com/r2/
- Hono Framework: https://hono.dev/
- Quasar Framework: https://quasar.dev/
- Chanfana (OpenAPI): https://chanfana.pages.dev/
To quickly understand a specific part of the codebase:
- Backend API logic: Start with
packages/worker/src/index.tsand exploremodules/ - Frontend UI: Check
packages/dashboard/src/pages/for page components - Configuration: Review
packages/worker/src/types.d.tsfor available options - User deployment: See
template/for end-user setup - Build process: Check root
package.jsonscripts and individual package builds
When making changes, always:
- Run linter:
pnpm lint - Build affected packages:
pnpm build-[package] - Test locally with
wrangler dev - Update documentation if adding features