A beautiful media browser modal for jQuery file uploaders.
Browse, search, and reuse previously uploaded files — without re-uploading.
Every file uploader lets you upload new files. But what about files you've already uploaded?
Users re-upload the same logo, the same product photo, the same PDF — over and over. There's no way to browse what's already on the server and pick from it.
jQuery Media Library solves this with a single JS file:
- Beautiful modal — responsive grid, lazy-loaded thumbnails, file type icons
- Search & filter — by filename and folder, with 300ms debounce
- Single or multi-select — toggle with one option
- Auto-hooks into jQuery uploaders — adds a "Media Library" button automatically
- Backend agnostic — works with PHP, Node.js, Python, Go — anything that returns JSON
- ~10KB minified — no dependencies beyond jQuery + Bootstrap 5
Live Demo — Try it in your browser right now. No setup needed.
The live demo uses mock data with placeholder images — no real backend required. In production, it connects to your own API endpoint and shows your actual uploaded files.
https://github.com/raca12/jquery-media-library/raw/main/screenshots/Recording.mp4
Full workflow: open modal, browse files, select multiple, insert into uploader — zero re-uploads.
| Uploader Button (Empty) | Uploader with Files |
|---|---|
![]() |
![]() |
| Media Library Modal | Multi-Select |
|---|---|
![]() |
![]() |
<!-- Required: jQuery 3.x + Bootstrap 5.x -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Media Library -->
<script src="https://cdn.jsdelivr.net/npm/jquery-media-library/dist/media-library.min.js"></script>npm install jquery-media-libraryDownload media-library.min.js (10KB) and include it after jQuery and Bootstrap.
<script src="media-library.min.js"></script>MediaLibrary.defaults.apiUrl = '/api/media-list';$('#browse-btn').on('click', function () {
MediaLibrary.open({
multiple: true,
onSelect: function (files) {
// files = [{ url: '/uploads/photo.jpg', name: 'photo.jpg' }, ...]
console.log('Selected:', files);
}
});
});That's it. Three lines of code.
Opens the media browser modal.
| Option | Type | Default | Description |
|---|---|---|---|
multiple |
boolean |
false |
Allow selecting multiple files |
folder |
string |
'' |
Pre-select a folder tab |
onSelect |
function |
null |
Callback with selected files: function(files) where files = [{url, name}] |
apiUrl |
string |
— | Override API endpoint for this call |
ajaxData |
object |
{} |
Extra query params for this call |
ajaxHeaders |
object |
{} |
Extra headers (e.g., { Authorization: 'Bearer ...' }) |
Global configuration. Set these once before any .open() call:
MediaLibrary.defaults.apiUrl = '/api/media-list';
MediaLibrary.defaults.title = 'Choose a file';
MediaLibrary.defaults.perPage = 48;
MediaLibrary.defaults.selectText = 'Select';
MediaLibrary.defaults.cancelText = 'Cancel';
MediaLibrary.defaults.allText = 'All';
MediaLibrary.defaults.searchPlaceholder = 'Search files...';
MediaLibrary.defaults.emptyText = 'No files found';
MediaLibrary.defaults.errorText = 'Error loading files';
MediaLibrary.defaults.fileInfoText = '{count} files';
MediaLibrary.defaults.selectedInfoText = '{selected} selected — {count} files';
MediaLibrary.defaults.uploaderButtonText = 'Media Library';
MediaLibrary.defaults.uploaderButtonIcon = 'bi bi-images';
MediaLibrary.defaults.ajaxHeaders = { 'X-CSRF-TOKEN': 'your-token' };
MediaLibrary.defaults.ajaxData = { _token: 'csrf-value' };// Single select → files.length === 1
// Multi select → files.length >= 1
files = [
{ url: '/uploads/products/photo.jpg', name: 'photo.jpg' },
{ url: '/uploads/docs/report.pdf', name: 'report.pdf' }
];Your backend must respond to a GET request with JSON. The plugin sends:
GET /api/media-list?folder=products&search=photo&page=1
And expects:
{
"files": [
{
"url": "/uploads/products/photo.jpg",
"name": "photo.jpg",
"type": "image",
"size": 102400,
"modified": "2026-02-15"
}
],
"folders": ["products", "avatars", "documents"],
"current_folder": "products",
"total": 128,
"page": 1,
"pages": 3
}| Field | Type | Description |
|---|---|---|
url |
string |
Required. Public URL to the file |
name |
string |
Required. Filename for display |
type |
string |
"image" or "document" — determines thumbnail vs icon |
size |
number |
File size in bytes (used in tooltip) |
modified |
string |
Last modified date (for display) |
Ready-to-use backend implementations:
| Language | File | Framework |
|---|---|---|
| PHP | examples/php/media-list.php |
Vanilla PHP |
| Node.js | examples/node/media-list.js |
Express.js |
Security Tip: Always add authentication to your endpoint. The examples include placeholder comments showing where to add auth checks.
The killer feature: automatic integration with jQuery file uploaders.
If you use a jQuery uploader plugin that triggers uploader-init events, the Media Library button appears automatically:
┌─────────────────────────────────────────────┐
│ │
│ ┌───────┐ ┌───────┐ ┌─────────┐ ┌───┐ │
│ │ img1 │ │ img2 │ │ Media │ │ + │ │
│ │ │ │ │ │ Library │ │ │ │
│ └───────┘ └───────┘ └─────────┘ └───┘ │
│ │
└─────────────────────────────────────────────┘
↑ existing files ↑ browse ↑ upload new
- Auto-detection: Listens for
uploader-initevents on<input>elements - Button injection: Adds a "Media Library" button next to the upload button
- Seamless insert: Selected files are added to the uploader's file list with
status: 'initial'— no re-upload needed - Survives re-renders: The button is re-injected after every
refreshPreviewFileList()call via method wrapping
If your uploader doesn't trigger uploader-init, you can manually wire it:
$('#my-upload-input').on('click', '.my-browse-btn', function () {
MediaLibrary.open({
multiple: true,
onSelect: function (files) {
files.forEach(function (f) {
// Add file URL to your input
var current = $('#my-upload-input').val();
var sep = current ? ',' : '';
$('#my-upload-input').val(current + sep + f.url);
});
}
});
});The library uses Bootstrap Icons by default. Override globally:
// Before opening the modal
MediaLibrary.defaults.uploaderButtonIcon = 'fa fa-photo-film'; // Font AwesomeMediaLibrary.defaults.title = 'Médiathèque';
MediaLibrary.defaults.selectText = 'Choisir';
MediaLibrary.defaults.cancelText = 'Annuler';
MediaLibrary.defaults.allText = 'Tout';
MediaLibrary.defaults.searchPlaceholder = 'Rechercher...';
MediaLibrary.defaults.emptyText = 'Aucun fichier trouvé';
MediaLibrary.defaults.fileInfoText = '{count} fichiers';
MediaLibrary.defaults.selectedInfoText = '{selected} sélectionné(s) — {count} fichiers';
MediaLibrary.defaults.uploaderButtonText = 'Médiathèque';All elements have CSS classes prefixed with ml-. Override them after the script loads:
/* Change selection color */
.ml-item.selected { border-color: #e91e63; box-shadow: 0 0 0 2px rgba(233,30,99,.3); }
.ml-item .ml-check { background: #e91e63; }
/* Larger thumbnails */
.ml-item-img { height: 150px; }
/* Different grid columns */
.ml-grid { grid-template-columns: repeat(4, 1fr); }| Browser | Version |
|---|---|
| Chrome | 60+ |
| Firefox | 55+ |
| Safari | 12+ |
| Edge | 79+ |
| Opera | 47+ |
| Dependency | Version | Required |
|---|---|---|
| jQuery | 3.0+ | Yes |
| Bootstrap 5 (JS + CSS) | 5.0+ | Yes |
| Bootstrap Icons | 1.0+ | Yes (for file type icons) |
jquery-media-library/
├── dist/
│ ├── media-library.js # Full source (22KB)
│ └── media-library.min.js # Minified (10KB)
├── src/
│ └── media-library.js # Development source
├── examples/
│ ├── demo.html # Interactive demo page
│ ├── php/media-list.php # PHP backend example
│ └── node/media-list.js # Node.js backend example
├── screenshots/ # Screenshots for README
├── package.json
├── LICENSE # MIT
└── README.md
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Every admin panel needs a media library. WordPress has one. Strapi has one. But if you're building a custom admin with jQuery + Bootstrap? You're on your own.
This library was extracted from a production multi-tenant e-commerce platform where admins were re-uploading the same product images dozens of times. One JS file later, they could browse and reuse everything.
Zero config. Zero dependencies beyond jQuery + Bootstrap. Just works.
MIT © raca12
If this saved you time, consider giving it a star ⭐



