Generate presigned URLs for secure client-side uploads or downloads
Generates time-limited signed URLs for secure R2 bucket uploads or downloads.
/plugin marketplace add secondsky/claude-skills/plugin install cloudflare-r2@claude-skillsGenerate time-limited, signed URLs for secure client-side uploads or downloads without exposing R2 credentials.
{{operation}} (upload or download){{key}}{{expiry}}{{headers}}import { AwsClient } from 'aws4fetch';
// Configure AWS client for R2
const r2Client = new AwsClient({
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
});
// Generate presigned URL for upload
async function generateUploadURL(
bucket: string,
key: string,
expirySeconds: number
) {
const url = new URL(
`https://${bucket}.${env.ACCOUNT_ID}.r2.cloudflarestorage.com/${key}`
);
url.searchParams.set('X-Amz-Expires', expirySeconds.toString());
const signed = await r2Client.sign(
new Request(url, { method: 'PUT' }),
{ aws: { signQuery: true } }
);
return signed.url;
}
// Generate presigned URL for download
async function generateDownloadURL(
bucket: string,
key: string,
expirySeconds: number
) {
const url = new URL(
`https://${bucket}.${env.ACCOUNT_ID}.r2.cloudflarestorage.com/${key}`
);
url.searchParams.set('X-Amz-Expires', expirySeconds.toString());
const signed = await r2Client.sign(
new Request(url, { method: 'GET' }),
{ aws: { signQuery: true } }
);
return signed.url;
}
// Generate upload URL (valid for 1 hour)
const uploadURL = await generateUploadURL('my-bucket', 'user/file.pdf', 3600);
// Client-side upload
fetch(uploadURL, {
method: 'PUT',
body: fileData,
headers: {
'Content-Type': 'application/pdf',
},
});
// Generate download URL (valid for 24 hours)
const downloadURL = await generateDownloadURL('my-bucket', 'user/file.pdf', 86400);
// Provide URL to user
return c.json({ downloadURL });
# Test upload URL
curl -X PUT "{{presigned_url}}" \
-H "Content-Type: text/plain" \
--data "Test content"
# Test download URL
curl "{{presigned_url}}" --output downloaded-file.txt