āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/brennenrocks/utilcn/storage/generate-presigned-upload-url ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
import { generatePresignedUploadUrl } from '@/lib/generate-presigned-upload-url';
const { uploadUrl, key, fileUrl } = await generatePresignedUploadUrl({
fileName: file.name,
contentLength: file.size,
expiresIn: 3600
});
| Parameter | Type | Description | Default |
|----------------|----------|------------------------------------------------|---------|
| fileName | string | The original filename with extension | - |
| contentLength| number | The size of the file in bytes | - |
| expiresIn | number | URL expiration time in seconds | 3600 |
An object containing:
uploadUrl - The presigned URL for uploading the filekey - The unique key/path where the file will be storedfileUrl - The public URL where the file will be accessible after uploadThis function requires the following environment variables:
S3_REGION=your-region
S3_ENDPOINT=your-s3-endpoint
S3_ACCESS_KEY=your-access-key
S3_SECRET_KEY=your-secret-key
S3_BUCKET_NAME=your-bucket-name
S3_PUBLIC_URL=your-public-url
The function automatically detects content types for common file extensions:
@aws-sdk/client-s3@aws-sdk/s3-request-presignerimport { PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { getS3Client } from '@/lib/s3-client';
const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024; // 10MB
type PresignedUrlInput = {
fileName: string;
expiresIn?: number;
contentLength: number;
};
export async function generatePresignedUploadUrl({
fileName,
expiresIn = 3600,
contentLength,
}: PresignedUrlInput) {
if (contentLength > MAX_FILE_SIZE_BYTES) {
throw new Error('File size exceeds maximum allowed limit');
}
try {
const fileExt = fileName.split('.').pop()?.toLowerCase() ?? '';
const uniqueId = Date.now().toString();
const key = fileExt ? `${uniqueId}.${fileExt}` : uniqueId;
const contentType = getContentType(fileExt);
const command = new PutObjectCommand({
Bucket: process.env.S3_BUCKET_NAME,
Key: key,
ContentType: contentType,
ContentLength: contentLength,
});
const uploadUrl = await getSignedUrl(getS3Client(), command, { expiresIn });
const fileUrl = `${process.env.S3_PUBLIC_URL}/${key}`;
return { uploadUrl, key, fileUrl };
} catch (err) {
console.error({ err }, 'Failed to generate presigned upload URL');
throw new Error('Failed to generate upload URL');
}
}
function getContentType(extension: string): string {
switch (extension) {
// Images
case 'jpg':
case 'jpeg':
return 'image/jpeg';
case 'png':
return 'image/png';
case 'gif':
return 'image/gif';
case 'webp':
return 'image/webp';
case 'svg':
return 'image/svg+xml';
// Documents
case 'pdf':
return 'application/pdf';
case 'doc':
return 'application/msword';
case 'docx':
return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
// Other common types
case 'json':
return 'application/json';
case 'txt':
return 'text/plain';
default:
return 'application/octet-stream';
}
}
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā