Media Library System Development for CMS
A media library is a centralized storage and file management interface: images, videos, documents. Instead of uploading a single file for each entry, the editor selects from already uploaded files — fewer duplicates, lower storage costs.
Data Model
media_files (
id, original_name, file_name, disk, path,
mime_type, size,
width, height, -- for images
alt, title, caption, -- SEO and accessibility
folder_id,
uploaded_by,
created_at, updated_at
)
media_folders (
id, name, parent_id, created_at
)
media_conversions (
id, media_id, name, -- 'thumb', 'medium', 'large'
file_name, width, height, size, created_at
)
File Upload via Presigned URL
Direct upload from browser to S3 — without server load:
// Server generates presigned URL
$s3 = new S3Client([...]);
$cmd = $s3->getCommand('PutObject', [
'Bucket' => env('AWS_BUCKET'),
'Key' => "uploads/temp/{$uuid}/{$filename}",
'ContentType' => $mimeType
]);
$presigned = $s3->createPresignedRequest($cmd, '+15 minutes');
return ['upload_url' => (string)$presigned->getUri(), 'key' => $key];
// Client uploads directly to S3
const { upload_url, key } = await getPresignedUrl(file);
await fetch(upload_url, { method: 'PUT', body: file, headers: { 'Content-Type': file.type } });
// Confirm upload to server
await confirmUpload(key, { alt: '', title: file.name });
Preview Generation
After upload — asynchronous version generation via queue:
class GenerateImageConversions implements ShouldQueue
{
public function handle(): void
{
$conversions = [
'thumb' => ['width' => 150, 'height' => 150],
'medium' => ['width' => 800, 'height' => null],
'large' => ['width' => 1920, 'height' => null],
'webp' => ['width' => null, 'height' => null, 'format' => 'webp']
];
foreach ($conversions as $name => $params) {
$image = Image::make(Storage::get($this->media->path));
if ($params['width']) {
$image->resize($params['width'], $params['height'], fn($c) => $c->aspectRatio());
}
if (isset($params['format'])) {
$image->encode($params['format'], 85);
}
Storage::put("media/conversions/{$this->media->id}/{$name}", $image->stream());
}
}
}
Media Library Interface
Main UI features:
- File grid with previews (or list)
- Filters: file type, folder, date
- Search by name and alt-text
- Drag-and-drop or button upload
- Folder creation
- Multiple selection for batch operations
- Edit alt, title, caption directly in library
- Copy URL to clipboard
Media Picker Widget for Editor
function MediaPickerButton({ onSelect, multiple = false }) {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button onClick={() => setIsOpen(true)}>Select from Library</Button>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent className="max-w-4xl h-[80vh]">
<MediaLibraryGrid
onSelect={(files) => {
onSelect(multiple ? files : files[0]);
setIsOpen(false);
}}
multiple={multiple}
/>
</DialogContent>
</Dialog>
</>
);
}
Cleanup of Unused Files
// Scheduled job: find files without references
$unused = MediaFile::whereDoesntHave('usages')
->where('created_at', '<', now()->subDays(30))
->get();
foreach ($unused as $file) {
Storage::delete($file->path);
$file->delete();
}
Development timeline: 3–4 weeks for complete media library with preview generation, folders, and picker widget.







