A lightweight, zero-dependency TypeScript library for generating presigned URLs for Cloudflare R2 storage using AWS Signature Version 4. Compatible with Cloudflare Workers and Node.js environments.
- ✅ Zero Dependencies - Uses only Web Crypto API and built-in browser/Node.js APIs
- ✅ Cloudflare Workers Compatible - No Node.js-specific dependencies
- ✅ TypeScript Support - Full type definitions included
- ✅ AWS Signature Version 4 - Standard-compliant signature generation
- ✅ Multiple HTTP Methods - Support for PUT, GET, and POST requests
- ✅ Comprehensive Validation - Input validation and error handling
- ✅ Testing Utilities - Built-in test function for debugging
npm install r2-presigned-urlimport { generateR2PresignedUrl, R2Credentials } from 'r2-presigned-url';
const credentials: R2Credentials = {
R2_ACCESS_KEY_ID: 'your-access-key',
R2_SECRET_ACCESS_KEY: 'your-secret-key',
R2_ACCOUNT_ID: 'your-account-id',
R2_BUCKET: 'my-bucket'
};
const url = await generateR2PresignedUrl(
{
key: 'uploads/file.jpg',
contentType: 'image/jpeg',
expiresIn: 3600 // 1 hour
},
credentials
);
console.log('Presigned URL:', url);Generates a presigned URL for R2 storage operations.
-
options(PresignedUrlOptions):key(string): The object key (path) in the R2 bucketcontentType(string): MIME type of the fileexpiresIn(number): URL expiration time in seconds (max 604800 = 7 days)method(optional): HTTP method - 'PUT' (default), 'GET', or 'POST'
-
credentials(R2Credentials):R2_ACCESS_KEY_ID(string): Cloudflare R2 Access Key IDR2_SECRET_ACCESS_KEY(string): Cloudflare R2 Secret Access KeyR2_ACCOUNT_ID(string): Cloudflare Account IDR2_BUCKET(optional): R2 Bucket name (defaults to 'development')
Promise<string>: The presigned URL
Tests the presigned URL generation with sample data.
credentials(R2Credentials): R2 credentials to test with
Promise<TestResult>: Test results with success status and debug info
import { generateR2PresignedUrl } from 'r2-presigned-url';
const uploadUrl = await generateR2PresignedUrl(
{
key: 'uploads/document.pdf',
contentType: 'application/pdf',
expiresIn: 1800 // 30 minutes
},
credentials
);
// Use the URL to upload the file
const response = await fetch(uploadUrl, {
method: 'PUT',
body: file,
headers: {
'Content-Type': 'application/pdf'
}
});const downloadUrl = await generateR2PresignedUrl(
{
key: 'uploads/document.pdf',
contentType: 'application/pdf',
expiresIn: 3600,
method: 'GET'
},
credentials
);
// Use the URL to download the file
const response = await fetch(downloadUrl);
const file = await response.blob();import { testPresignedUrlGeneration } from 'r2-presigned-url';
const result = await testPresignedUrlGeneration(credentials);
if (result.success) {
console.log('✅ Test passed!');
console.log('Generated URL:', result.url);
console.log('Signature:', result.debug?.signature);
} else {
console.error('❌ Test failed:', result.error);
}// workers/index.ts
import { generateR2PresignedUrl } from 'r2-presigned-url';
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method === 'POST') {
const { key, contentType } = await request.json();
const credentials = {
R2_ACCESS_KEY_ID: env.R2_ACCESS_KEY_ID,
R2_SECRET_ACCESS_KEY: env.R2_SECRET_ACCESS_KEY,
R2_ACCOUNT_ID: env.R2_ACCOUNT_ID,
R2_BUCKET: env.R2_BUCKET
};
try {
const url = await generateR2PresignedUrl(
{
key,
contentType,
expiresIn: 3600
},
credentials
);
return new Response(JSON.stringify({ url }), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
);
}
}
return new Response('Method not allowed', { status: 405 });
}
};For Cloudflare Workers, add these to your wrangler.toml:
[vars]
R2_ACCESS_KEY_ID = "your-access-key"
R2_SECRET_ACCESS_KEY = "your-secret-key"
R2_ACCOUNT_ID = "your-account-id"
R2_BUCKET = "your-bucket-name"The library throws descriptive errors for common issues:
try {
const url = await generateR2PresignedUrl(options, credentials);
} catch (error) {
if (error.message.includes('Missing required R2 credentials')) {
// Handle missing credentials
} else if (error.message.includes('Invalid parameters')) {
// Handle invalid parameters
} else {
// Handle other errors
}
}This library uses the Web Crypto API, which is supported in:
- Chrome 37+
- Firefox 34+
- Safari 11+
- Edge 12+
- Node.js 15+
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
MIT License - see LICENSE file for details.
- Initial release
- AWS Signature Version 4 implementation
- Cloudflare Workers compatibility
- TypeScript support
- Comprehensive error handling