Make A Media Manager With Cloudflare R2

Stack: Vanilla JS + Cloudflare R2 + AWS SDK

Build a Client-Side Cloudflare R2 Media Manager with JavaScript File Uploads

Master Cloudflare R2 storage by building a browser-based media manager with vanilla JavaScript and the AWS SDK. This tutorial teaches S3-compatible API integration, client-side uploads, image optimization, and file management—all without a backend server. Perfect for managing static site assets at a fraction of S3 costs.

What You'll Build

  • Client-side media manager with drag-and-drop uploads
  • File browser with thumbnail previews and metadata
  • Image optimization and automatic resizing
  • File deletion and bulk operations
  • Shareable public URLs for uploaded files
  • Local-only tool with secure credential management

Cloudflare R2 and AWS SDK Skills You'll Learn

  • R2 Setup: Create buckets, configure CORS, and generate API tokens
  • AWS SDK Integration: Use S3Client for R2 operations in the browser
  • File Uploads: PutObjectCommand with progress tracking
  • File Listing: ListObjectsV2Command for browsing files
  • Image Processing: Client-side optimization with Canvas API
  • Drag and Drop: HTML5 File API for intuitive uploads
  • Security: CORS policies and credential best practices

Prerequisites

  • Cloudflare account (free tier available)
  • Basic JavaScript knowledge (async/await, promises)
  • Understanding of HTML and CSS fundamentals
  • Familiarity with npm and module imports
  • Code editor and local development server

Time Commitment: 3-4 hours. Build a practical tool for managing media files on Cloudflare R2.

Why Cloudflare R2 for Media Storage?

Cloudflare R2 offers S3-compatible storage with zero egress fees, making it dramatically cheaper than AWS S3 for public content. At $0.015/GB per month (with 10GB free), R2 can save you up to 90% compared to S3's egress charges. Combined with Cloudflare's global CDN and 300+ edge locations, R2 delivers fast, reliable media hosting worldwide.

This tutorial teaches you to build practical tooling for R2, perfect for managing images, assets, and media for static sites, blogs, or portfolios. The S3-compatible API means you can migrate from S3 with minimal changes.

Make A Media Manager With Cloudflare R2

Build a client-side media manager for Cloudflare R2 using pure JavaScript. No backend needed—upload, browse, preview, and delete files directly from your browser using the S3-compatible API. Perfect for managing images and assets for static sites, with drag-and-drop uploads, image optimization, metadata tagging, and shareable URLs. Keep your media organized in blazing-fast R2 storage at a fraction of S3 costs.

Web Development Web Setup: Installation and Project Configuration

1

Create bucket and get API credentials

Get your Cloudflare R2 bucket created and grab your API credentials. You'll set up a bucket for storing media files and generate an API token with read/write permissions.
AI Prompt
Set up Cloudflare R2 bucket and API credentials.

Log into Cloudflare dashboard and navigate to R2 Object Storage.

Create a new R2 bucket with a descriptive name like "my-media-files".

Configure bucket settings: keep it private initially (we'll configure public access for specific files later if needed).

Generate R2 API token:
- Go to R2 → Manage R2 API Tokens
- Click "Create API Token"
- Give it a name like "Media Manager Token"
- Set permissions to "Object Read & Write" for your specific bucket
- Copy the Access Key ID, Secret Access Key, and endpoint URL
- Store these securely - you'll need them for the application

Note the bucket endpoint format: https://<account-id>.r2.cloudflarestorage.com

Document your credentials:
- Account ID
- Access Key ID
- Secret Access Key
- Bucket name
- Endpoint URL

Keep these credentials secure and never commit them to version control.
2

Enable browser access to R2

Configure CORS on your R2 bucket so your browser-based JavaScript can make requests directly to R2. This allows the frontend to upload, list, and delete files without a backend server.
AI Prompt
Configure CORS policy for R2 bucket to allow browser requests.

In Cloudflare R2 dashboard, select your bucket and go to Settings.

Find the CORS policy section and add a new CORS rule:

For local development, use permissive settings:
- Allowed Origins: http://localhost:* or http://127.0.0.1:* (or use * for testing)
- Allowed Methods: GET, PUT, POST, DELETE, HEAD
- Allowed Headers: * (or specifically: content-type, authorization, x-amz-*)
- Expose Headers: ETag, x-amz-request-id
- Max Age: 3600 seconds

Example CORS JSON configuration:
[
  {
    "AllowedOrigins": ["http://localhost:*", "http://127.0.0.1:*"],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
    "AllowedHeaders": ["*"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3600
  }
]

Apply the CORS policy to your bucket.

Important security note: For production, restrict AllowedOrigins to your specific domain instead of using wildcards.

Test CORS configuration is working by making a simple OPTIONS request from your browser console.
3

Create HTML/CSS/JS structure

Create a simple single-page application structure with HTML, CSS, and JavaScript. Keep it clean and organized with separate files for styles and logic.
AI Prompt
Create a frontend-only project structure for the R2 media manager.

Create project directory named "r2-media-manager" with the following structure:

r2-media-manager/
├── index.html          (main HTML file)
├── styles.css          (styling)
├── app.js             (main application logic)
├── r2-client.js       (R2 API wrapper)
└── config.js          (configuration - gitignored)

Create index.html with:
- HTML5 boilerplate
- Links to styles.css
- Container for file upload area (drag & drop zone)
- Grid/list area to display uploaded files
- Script tags for AWS SDK from CDN
- Script tags for your app.js and r2-client.js

Create styles.css with:
- Modern, clean styling
- Responsive grid layout for file thumbnails
- Upload drop zone styling with hover effects
- Button and modal styles
- Mobile-friendly design

Create config.js template:
export const R2_CONFIG = {
  accountId: 'YOUR_ACCOUNT_ID',
  accessKeyId: 'YOUR_ACCESS_KEY_ID',
  secretAccessKey: 'YOUR_SECRET_ACCESS_KEY',
  bucketName: 'YOUR_BUCKET_NAME',
  endpoint: 'https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com'
}

Add config.js to .gitignore to keep credentials secure.

Create a config.example.js showing the structure without real credentials.

Database and Environment Configuration for Web Development

4

Initialize S3 client for R2

Set up the AWS SDK for JavaScript to connect to Cloudflare R2. Since R2 is S3-compatible, we'll use the AWS S3 client with custom endpoint configuration pointing to R2.
AI Prompt
Initialize AWS SDK S3 client configured for Cloudflare R2.

In r2-client.js, import AWS SDK (or use from CDN):
- Use AWS SDK v3 (modular) or v2 (monolithic) from CDN
- For CDN: <script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.x.min.js"></script>

Create R2Client class that initializes AWS S3 client:

Import configuration from config.js with credentials.

Configure AWS.S3 or S3Client with:
- endpoint: Your R2 endpoint URL
- region: 'auto' (R2 uses auto region)
- credentials: {
    accessKeyId: from config
    secretAccessKey: from config
  }
- signatureVersion: 'v4'
- s3ForcePathStyle: true (important for R2)

Create initialization function that validates credentials.

Add error handling for invalid credentials or network issues.

Test connection by attempting to list bucket contents (can be empty initially).

Export the configured S3 client for use in app.js.

Handle CORS errors gracefully with user-friendly messages.

Building Web Features: Core Functionality and Admin Panel

5

Implement drag-drop and click upload

Build the file upload functionality with both drag-and-drop and traditional file picker. Upload files directly to R2 from the browser using the S3 putObject API.
AI Prompt
Implement file upload functionality with drag-and-drop support.

In app.js, create upload handler:

Set up file input element with accept attribute for images (image/*) or all files (*).

Add drag-and-drop event listeners on drop zone:
- dragover: prevent default and add visual feedback
- dragleave: remove visual feedback
- drop: prevent default, get files from event.dataTransfer.files

On file selection (drop or click), read the files:
- Iterate through FileList
- Validate file type and size (e.g., max 10MB)
- Show preview for images using FileReader
- Display upload progress indicator

Upload files to R2 using S3 client:
- Use s3.putObject() or s3.upload() method
- Parameters: {
    Bucket: bucketName,
    Key: filename (consider adding timestamp or UUID to prevent collisions),
    Body: file,
    ContentType: file.type,
    Metadata: optional metadata
  }

Show upload progress:
- Use upload progress events if available
- Display progress bar or percentage
- Show success/error states

Handle upload completion:
- Add uploaded file to the displayed file list
- Clear the file input
- Show success notification

Implement error handling:
- Network errors
- Permission errors
- File size exceeded
- Invalid file types

Add batch upload support for multiple files.

Optional: Generate thumbnails for images before displaying in grid.
6

Display all files from bucket

List all files in your R2 bucket and display them in a nice grid or list view. Show thumbnails for images, file names, sizes, and upload dates. Make it responsive and easy to browse.
AI Prompt
Implement file listing and display functionality.

Create listFiles() function using S3 client:
- Use s3.listObjectsV2() method
- Parameters: {
    Bucket: bucketName,
    MaxKeys: 1000 (adjust as needed)
  }
- Handle pagination with ContinuationToken for buckets with many files

Parse response:
- Extract file metadata: Key, Size, LastModified, ETag
- Calculate human-readable file sizes (KB, MB, GB)
- Format dates nicely

Display files in the UI:
- Create grid or list layout
- For images: show thumbnail preview using R2 public URL or generate presigned URL
- For non-images: show file type icon
- Display filename, size, and date
- Add hover effects and selection states

Generate presigned URLs for file access:
- Use s3.getSignedUrl() or s3.getSignedUrlPromise()
- Parameters: {
    Bucket: bucketName,
    Key: filename,
    Expires: 3600 (1 hour)
  }
- Use these URLs to display/download files

Implement view switching:
- Toggle between grid and list views
- Save user preference in localStorage

Add search/filter functionality:
- Filter by filename
- Filter by file type (images, documents, etc.)
- Sort by date, name, or size

Add pagination or infinite scroll if handling many files.

Handle empty state when bucket is empty with friendly message.

Implement refresh button to reload file list.
7

View images and download files

Add preview functionality to view images in a modal/lightbox and download files. Generate presigned URLs for secure temporary access to private files.
AI Prompt
Implement file preview and download functionality.

Create preview modal/lightbox:
- Modal overlay that darkens background
- Large preview area for images
- File information sidebar (name, size, date, dimensions)
- Close button and keyboard navigation (ESC to close)

For image files:
- Display full-size image in modal
- Add zoom controls
- Add previous/next navigation for browsing through images
- Show loading spinner while image loads

For non-image files:
- Show file icon and metadata
- Provide download button

Generate presigned URLs for secure access:
- Use s3.getSignedUrl('getObject', params)
- Set expiration time (e.g., 1 hour)
- Use this URL as image src or download href

Implement download functionality:
- Create download button for each file
- Generate presigned download URL
- Add Content-Disposition header for proper filename
- Trigger browser download using anchor element or fetch + blob

Add click handlers:
- Click on file thumbnail to open preview
- Click on filename to download
- Right-click options for copy URL

Handle different file types:
- Images: inline preview
- Videos: video player (if needed)
- Documents: download only
- Text files: could show in preview with syntax highlighting

Implement keyboard shortcuts:
- Arrow keys: navigate between files
- ESC: close preview
- Space: toggle info panel

Add copy-to-clipboard for file URLs.

Show error states if file fails to load.
8

Remove files from R2 bucket

Add the ability to delete files from your R2 bucket. Include confirmation dialogs to prevent accidental deletions and support batch deletion for multiple files.
AI Prompt
Implement file deletion functionality with safety confirmations.

Add delete button to each file in the list:
- Trash icon button on hover or always visible
- Style with warning colors (red/orange)

Implement delete confirmation:
- Show modal dialog asking "Are you sure?"
- Display filename being deleted
- Include "Cancel" and "Delete" buttons
- Make "Delete" button require explicit click (not default)

Delete single file:
- Use s3.deleteObject() method
- Parameters: {
    Bucket: bucketName,
    Key: filename
  }
- Handle response and errors

Update UI after deletion:
- Remove file from displayed list immediately (optimistic update)
- Show success notification
- If delete fails, restore file in list and show error

Implement batch/multi-delete:
- Add checkbox selection to each file
- "Select All" option
- Delete button that works on all selected files
- Confirmation dialog shows count: "Delete 5 files?"
- Use s3.deleteObjects() for batch deletion (more efficient)

Add undo functionality (optional):
- Brief timeout before actual deletion
- Show "Undoing..." notification
- Keep deleted file data in memory temporarily

Handle errors gracefully:
- Permission denied
- File not found (already deleted)
- Network errors

Add loading states during deletion:
- Disable delete button while processing
- Show spinner or progress indicator

Security considerations:
- Ensure only authenticated users can delete (if adding auth)
- Log deletions (optional)
- Consider soft delete with trash/recovery (advanced)

Keyboard shortcut: Delete key to delete selected files.
9

Add tags, descriptions, and custom fields

Add metadata management so you can tag files, add descriptions, and store custom fields. This makes it easier to organize and search your media later.
AI Prompt
Implement metadata management for uploaded files.

Add metadata when uploading files:
- Use Metadata parameter in s3.putObject()
- Store custom fields: description, tags, alt-text, category
- Metadata keys must be lowercase and use hyphens
- Example: { 'x-amz-meta-description': 'My image', 'x-amz-meta-tags': 'blog,tutorial' }

Create metadata editor UI:
- Edit button on each file
- Modal form with fields:
  * Description (textarea)
  * Tags (comma-separated or tag picker)
  * Alt text (for images)
  * Custom categories
- Save and Cancel buttons

Update metadata for existing files:
- Use s3.copyObject() to copy file to itself with new metadata
- Parameters: {
    Bucket: bucketName,
    CopySource: bucketName + '/' + filename,
    Key: filename,
    Metadata: updatedMetadata,
    MetadataDirective: 'REPLACE'
  }

Retrieve metadata when listing files:
- Use s3.headObject() to get metadata without downloading file
- Display metadata in file preview/info panel

Implement tag-based filtering:
- Extract all unique tags from files
- Show tag cloud or tag list
- Click tag to filter files by that tag
- Support multiple tag selection

Add search by metadata:
- Search in descriptions
- Filter by tags
- Filter by custom fields

Store metadata locally for faster filtering:
- Cache metadata in localStorage
- Sync with R2 on refresh
- Handle conflicts if file updated externally

Add bulk metadata editing:
- Select multiple files
- Add tags to all selected files
- Update common fields

Provide metadata export:
- Export all metadata as JSON
- Useful for backup or migration

Handle special characters in metadata values.

Consider using custom metadata schema that works for your use case.
10

Create shareable public URLs

Generate public URLs for your files that you can use in your Gatsby blog or share elsewhere. Support both presigned temporary URLs and permanent public URLs with custom domains.
AI Prompt
Implement URL generation for files in R2 bucket.

Configure R2 public access (optional):
- In R2 dashboard, enable "Public Access" on bucket if you want permanent URLs
- Or keep private and use presigned URLs only

Generate presigned URLs for temporary access:
- Use s3.getSignedUrl('getObject', { Bucket, Key, Expires })
- Set expiration time (300 seconds to 7 days)
- Display expiration time to user
- Add "Regenerate URL" button when expired

Generate public URLs if bucket allows:
- Format: https://pub-XXXXX.r2.dev/filename
- Or custom domain: https://media.yourdomain.com/filename

Add custom domain support:
- Configure custom domain in R2 dashboard
- Allow user to set custom domain in config
- Generate URLs using custom domain

Create URL generator UI:
- Button to "Get Shareable Link"
- Modal showing generated URL
- Copy to clipboard button
- QR code for mobile sharing (optional)

URL format options:
- Direct file URL
- Markdown format: ![alt](url)
- HTML format: <img src="url" alt="alt">
- Gatsby/React format: <img src={url} alt="alt" />

Add URL expiration selector for presigned URLs:
- 5 minutes, 1 hour, 24 hours, 7 days
- Custom time input

Implement batch URL generation:
- Select multiple files
- Generate URLs for all
- Export as CSV or JSON
- Copy all as markdown list

Add CDN URL transformation (if using Cloudflare CDN):
- Image resizing parameters
- Format conversion (WebP)
- Quality adjustments
- Example: /cdn-cgi/image/width=800,format=auto/file.jpg

Store generated URLs history in localStorage for quick access.

Add "Copy as..." dropdown with different formats.

Validate URL before copying (test if accessible).

Optimization

11

Cache file list and metadata locally

Implement local caching to make the app faster and reduce API calls to R2. Store file lists and metadata in localStorage and sync intelligently.
AI Prompt
Implement local caching with localStorage for better performance.

Create cache management system:
- Store file list in localStorage after fetching from R2
- Cache key format: 'r2-media-cache-v1'
- Include timestamp for cache invalidation

Cache structure:
{
  timestamp: Date.now(),
  files: [...file list with metadata],
  lastSync: timestamp,
  version: 1
}

Implement cache loading:
- On app load, check localStorage first
- Display cached files immediately (instant UI)
- Fetch from R2 in background
- Compare and update if changes detected

Cache invalidation strategies:
- Time-based: expire after 5 minutes, 1 hour, etc.
- Manual: "Refresh" button to force reload
- Smart sync: use ETags to detect changes
- Automatic: sync on window focus

Implement sync indicator:
- Show "Syncing..." status
- Display last sync time
- Show offline/online status

Handle cache size limits:
- localStorage limit is ~5-10MB
- Store only essential data
- Compress if needed (JSON stringified)
- Implement LRU eviction if cache grows too large

Cache file metadata separately:
- Full metadata for recently viewed files
- Minimal metadata for others

Add offline support:
- Detect when offline (navigator.onLine)
- Show cached data with warning
- Queue uploads/deletes for when online
- Retry failed operations automatically

Cache presigned URLs:
- Store with expiration time
- Regenerate before expiration
- Don't cache if expiring within 5 minutes

Implement cache versioning:
- Invalidate old cache format when updating app
- Migrate old cache to new format if possible

Add cache clear functionality:
- Settings button to clear cache
- Clear cache on logout (if adding auth)

Debug cache in development:
- Console log cache hits/misses
- Add cache viewer in UI (for debugging)

Test cache behavior:
- Works offline
- Syncs when coming online
- Updates when files change
- Doesn't show stale data
12

Compress and resize before upload

Add client-side image optimization to compress and resize images before uploading to R2. This saves bandwidth and storage while maintaining good quality.
AI Prompt
Implement client-side image optimization before uploading to R2.

Add image processing library:
- Use browser Canvas API (native, no dependencies)
- Or add library like browser-image-compression from CDN

Create image optimizer function:
- Input: File object
- Output: Optimized File/Blob

Implement resize functionality:
- Max width/height options (e.g., 1920px, 2400px)
- Maintain aspect ratio
- Use Canvas API to draw resized image:
  * Create canvas element
  * Get 2D context
  * Calculate new dimensions
  * drawImage() with new size

Implement compression:
- Use canvas.toBlob() with quality parameter
- JPEG: quality 0.8-0.9 (80-90%)
- PNG: may not compress much (use WebP instead)
- WebP: quality 0.8 with best compression

Add format conversion:
- Option to convert PNG to JPEG or WebP
- Convert transparent backgrounds to white for JPEG
- Keep transparency for WebP/PNG

Create optimization settings UI:
- Toggle: "Optimize images before upload"
- Slider: Max dimension (1000px - 4000px)
- Slider: Quality (60% - 100%)
- Dropdown: Output format (Original, JPEG, WebP)
- Save settings to localStorage

Show optimization preview:
- Before/after file size comparison
- Visual quality preview (side-by-side)
- Compression ratio percentage
- Estimated upload time savings

Process images on file selection:
- Check if file is image
- Apply optimization if enabled
- Update file size display
- Keep original filename with optimized extension

Handle edge cases:
- Very large images (>10MB, >5000px)
- Animated GIFs (don't optimize, or warn user)
- SVG files (don't process, already optimized)
- Small images (skip if under certain size)

Add batch optimization:
- Process multiple images in parallel
- Show progress: "Optimizing 3/10 images..."
- Use Web Workers for better performance (advanced)

Generate thumbnails during optimization:
- Create small preview (200px)
- Upload both full and thumbnail versions
- Use thumbnail for file list display

Add EXIF data handling:
- Strip EXIF data for privacy (default)
- Option to preserve EXIF data
- Use EXIF.js library if needed

Show optimization stats:
- Total space saved
- Average compression ratio
- Total optimized images count

Error handling:
- Fallback to original if optimization fails
- Warn if image quality too degraded
- Handle browser compatibility issues

Polish

13

Add dark mode and responsive design

Polish the user interface with dark mode support, smooth animations, responsive design for mobile, and accessibility improvements.
AI Prompt
Add UI polish and improve user experience.

Implement dark mode:
- Add toggle button in header/settings
- Store preference in localStorage
- Use CSS custom properties for colors:
  * --bg-primary, --bg-secondary
  * --text-primary, --text-secondary
  * --accent-color, --border-color
- Toggle .dark class on root element
- Smooth transition between modes
- Respect system preference: prefers-color-scheme

Make fully responsive:
- Mobile-first design approach
- Breakpoints: 640px, 768px, 1024px, 1280px
- Grid adjusts: 1 column (mobile), 2 (tablet), 3-4 (desktop)
- Upload zone scales with screen size
- Modals full-screen on mobile
- Touch-friendly button sizes (44px minimum)

Add smooth animations:
- Fade in files as they load
- Slide in/out for modals
- Hover scale effects on thumbnails
- Upload progress animations
- Skeleton loaders while loading
- Use CSS transitions and transforms

Improve drag-and-drop UX:
- Clear visual feedback on dragover
- Animated dashed border
- "Drop files here" message appears
- Highlight drop zone
- Show file count while dragging

Add loading states everywhere:
- Skeleton screens for file grid while loading
- Spinners for long operations
- Progress bars for uploads
- Shimmer effect for loading thumbnails

Implement error handling UI:
- Toast notifications for errors/success
- Error boundaries for graceful degradation
- Retry buttons for failed operations
- Helpful error messages (not technical jargon)

Add empty states:
- Friendly message when bucket is empty
- Call-to-action to upload first file
- Illustration or icon
- Quick start guide

Improve accessibility:
- Proper ARIA labels
- Keyboard navigation support
- Focus visible styles
- Alt text for images
- Screen reader announcements
- Sufficient color contrast

Add keyboard shortcuts:
- Ctrl/Cmd + U: Upload
- Escape: Close modal
- Arrow keys: Navigate files
- Delete: Delete selected
- Ctrl/Cmd + A: Select all
- Show keyboard shortcuts help (Ctrl+/)

Add settings panel:
- Dark mode toggle
- Grid/list view preference
- Default upload settings
- Cache management
- API credential update
- Export/import settings

Polish small details:
- Favicon and app icon
- Loading spinner style
- Cursor changes on hover
- Smooth scroll behavior
- Nice fonts (Inter, System UI)
- Consistent spacing and sizing

Add copy-to-clipboard feedback:
- "Copied!" tooltip or toast
- Icon changes briefly
- Subtle animation

Test on multiple devices:
- iOS Safari
- Android Chrome
- Desktop browsers
- Different screen sizes

Security

14

Implement best practices for credentials

Secure your media manager by implementing best practices for credential storage, input validation, and safe file handling. Since this is local-only, focus on preventing accidental exposure.
AI Prompt
Implement security best practices for the media manager.

Secure credential management:
- Never commit credentials to git
- Add config.js to .gitignore
- Create config.example.js template without real credentials
- Show warning if default credentials detected
- Add instructions for setting up credentials

Validate all user inputs:
- Sanitize filenames (remove special characters)
- Limit filename length (max 255 characters)
- Check file extensions against allowlist
- Validate file size before upload
- Prevent path traversal (../, absolute paths)

Implement file type validation:
- Check MIME type from file.type
- Verify file magic bytes (header) for images
- Reject executable files (.exe, .sh, .bat)
- Allowlist: images, videos, documents only
- Show clear error for blocked types

Content Security Policy (CSP):
- Add CSP meta tag in HTML
- Restrict script sources
- Allow R2 endpoints for images
- Prevent inline scripts (use nonce if needed)

Prevent XSS attacks:
- Never use innerHTML with user input
- Use textContent for filenames/descriptions
- Sanitize metadata before displaying
- Escape HTML in user-generated content

File upload security:
- Generate unique filenames (UUID + timestamp)
- Prevent overwriting existing files
- Scan filenames for malicious content
- Limit concurrent uploads
- Implement rate limiting (client-side)

S3 API security:
- Use least-privilege API tokens
- Set expiration on presigned URLs
- Use HTTPS only (enforce)
- Don't log credentials in console
- Rotate API keys periodically

Prevent sensitive data exposure:
- Don't expose full file paths
- Sanitize error messages (don't leak credentials)
- Remove debug logs in production
- Don't display raw API responses

Session security (if adding auth):
- Use secure session storage
- Implement logout functionality
- Clear cache on logout
- Auto-logout after inactivity

Add security headers:
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- Referrer-Policy: strict-origin-when-cross-origin

Implement audit logging:
- Log uploads, deletes, access (locally)
- Store in localStorage with timestamps
- Add "Activity Log" view
- Clear old logs periodically

Security for local development only:
- Add warning banner: "LOCAL DEV ONLY"
- Don't deploy to public servers
- Add robots.txt to prevent indexing
- Consider basic auth if sharing locally

Add security checklist:
- Remind user to rotate API keys
- Check CORS configuration
- Verify bucket permissions
- Review public access settings

Document security considerations:
- README with security notes
- Comment about credential storage
- Instructions for secure setup
- Warning about local-only use

Testing Your Web Development Web Application

15

Validate all functionality works

Test your media manager thoroughly to ensure all features work correctly. Test uploads, downloads, deletions, error handling, and edge cases.
AI Prompt
Test all functionality of the R2 media manager.

Test file upload:
- Single file upload works
- Multiple file upload works
- Drag and drop works
- Click to browse works
- Large files upload (100MB+)
- Upload progress shows correctly
- Cancellation works (if implemented)
- Duplicate filenames handled
- Special characters in filenames
- Very long filenames

Test file listing:
- Files display correctly after upload
- Thumbnails load for images
- File metadata shows (size, date)
- Empty bucket shows appropriate message
- Large number of files (100+)
- Pagination works (if implemented)
- Refresh updates list
- Sorting works

Test file preview:
- Images display in modal
- Download works
- Close modal works
- Keyboard navigation works
- Different image formats (JPG, PNG, WebP)
- Large images load
- Broken images handled gracefully

Test file deletion:
- Single delete works
- Confirmation prompt appears
- File removed from list
- Multiple delete works (if implemented)
- Delete handles errors gracefully
- Can't delete twice

Test metadata management:
- Adding metadata works
- Updating metadata works
- Searching by metadata works
- Tags display correctly
- Special characters in metadata

Test URL generation:
- Presigned URLs generated correctly
- URLs work (test by opening)
- Copy to clipboard works
- Different formats available
- URLs expire properly

Test caching:
- Files load from cache on refresh
- Cache updates when files change
- Manual refresh works
- Cache expires after timeout
- Works offline (shows cached data)

Test image optimization:
- Images compressed before upload
- Quality settings work
- Size reduction visible
- Original quality preserved well
- Different formats work

Test UI:
- Dark mode toggles correctly
- Responsive on mobile
- Buttons all work
- Animations smooth
- No console errors
- Loading states show

Test error handling:
- Invalid credentials show error
- Network errors handled
- Invalid files rejected
- Helpful error messages
- Retry works after error

Test edge cases:
- Empty bucket
- Very slow network
- Lost connection during upload
- Browser refresh during upload
- Multiple tabs open
- LocalStorage full
- CORS errors
- Invalid R2 configuration

Test browser compatibility:
- Chrome/Edge (Chromium)
- Firefox
- Safari (Mac/iOS)
- Mobile browsers

Test security:
- Credentials not in console
- Config.js in gitignore
- No XSS vulnerabilities
- File validation works

Create testing checklist document:
- List all features
- Check boxes for each test
- Note any issues found
- Track resolution

Document known issues:
- Limitations
- Browser-specific bugs
- Workarounds
- Future improvements

Documentation

16

Write setup and usage guide

Write comprehensive documentation so you (and others) can set up and use the media manager easily. Include setup instructions, usage guide, and troubleshooting.
AI Prompt
Create comprehensive documentation for the R2 media manager.

Create README.md with:

Project overview:
- What it does (manage Cloudflare R2 media)
- Key features list
- Use cases
- Screenshot or demo GIF

Prerequisites:
- Cloudflare account with R2 enabled
- API token with R2 permissions
- Modern web browser
- Text editor

Setup instructions:
1. Clone/download project
2. Create R2 bucket in Cloudflare
3. Generate API credentials
4. Copy config.example.js to config.js
5. Fill in credentials
6. Open index.html in browser (or use local server)

Configuration guide:
- How to get Account ID
- How to generate API token
- How to configure CORS
- How to set up custom domain (optional)

Usage guide:
- How to upload files (drag-drop, click)
- How to view/preview files
- How to delete files
- How to generate shareable URLs
- How to search and filter
- How to manage metadata

Features documentation:
- File upload with progress
- File listing and browsing
- Image preview and download
- Batch operations
- URL generation
- Dark mode
- Caching

Troubleshooting section:
- Common errors and solutions:
  * CORS errors → check CORS config
  * 403 Forbidden → check API permissions
  * Files not loading → check credentials
  * Upload fails → check file size, CORS
- How to check browser console
- How to verify R2 connectivity
- How to clear cache

Security notes:
- Keep config.js private
- Don't commit credentials
- Local development only warning
- Rotate API keys regularly

Limitations:
- Browser file size limits
- localStorage size limits
- No multi-user support
- Local only (not production-ready)

Advanced configuration:
- Custom domains
- Image optimization settings
- Cache configuration
- Keyboard shortcuts list

Contributing (if open source):
- How to contribute
- Code style guide
- Pull request process

License:
- Choose appropriate license (MIT, etc.)

Create separate docs:

SETUP.md - Detailed setup guide
- Step-by-step with screenshots
- Cloudflare dashboard walkthrough
- Credential generation guide

TROUBLESHOOTING.md - Common issues
- Error messages and solutions
- FAQ section
- How to report bugs

API.md - R2 API reference
- S3-compatible API docs
- Code examples
- Common operations

Add inline code comments:
- Explain complex logic
- Document function parameters
- Add TODO comments for future work

Create video tutorial (optional):
- Screen recording of setup
- Demo of key features
- Upload to YouTube

Write blog post (for tutorial):
- Why use R2 for media
- Benefits over S3
- Cost comparison
- Use cases