Modern, responsive web file manager built with Vue 3 + TypeScript frontend and PHP 8.5 backend. Supports light/dark themes, drag & drop uploads, image editing (Filerobot), and integration with TinyMCE 8 / CKEditor 5. Includes a ready-to-use TinyMCE plugin. No database required.
Developed by Radek Hulán with the amazing assistance of Claude Code.
- Features
- Quick Start
- Docker
- Security
- Requirements
- Configuration
- Editor Integration
- Web Server Configuration
- Production Deployment
- Keyboard Shortcuts
- Available Languages
- Development
- Testing
- API Reference
- Project Structure
- Building Distribution Package
- Technology Stack
- License
- Three view modes — Grid (thumbnails), List (detailed rows), and Columns
- Directory sidebar — collapsible tree with lazy-loaded folders, auto-expands to current path
- Breadcrumb navigation with one-click access to any parent folder
- Sort by name, date, or size (ascending / descending)
- Real-time search with instant filtering as you type
- Type filters — show only images, videos, audio, documents, or archives
- Remembers last folder across sessions
- Optimized for large directories — pagination and progressive rendering for 1 000+ files
- Upload files via drag & drop, file picker, or paste from URL
- Upload queue with per-file progress bars and batch controls
- Create, rename, delete files and folders (single or bulk)
- Copy / Cut / Paste files and folders across directories
- Duplicate files with one click
- Download any file directly from the browser
- Extract ZIP, GZ, and TAR archives
- Edit text files (create new or modify existing)
- Change permissions (chmod) on Linux servers
- Images — full-size preview with click-to-zoom
- Video & Audio — HTML5 player with playback controls
- Text / Code — syntax-colored preview
- PDF — native browser PDF viewer (no external service)
- Office documents — Google Docs Viewer integration
- Built-in Filerobot Image Editor for JPG, PNG, and WebP
- Crop, rotate, flip, resize with aspect ratio control
- Brightness, contrast, saturation, hue adjustments
- Filters (blur, sharpen, sepia, vintage, …)
- Annotations — shapes, text, brush, watermark
- Saves directly back to the server
- TinyMCE 8 — ready-to-use plugin with toolbar button and native file picker
- CKEditor 5 / 4 — postMessage-based integration
- Standalone popup — open in a window, receive selected file URL via postMessage
- Smart insertion: images as
<img>, video as<video>, audio as<audio>, links as<a> - Automatic relative URL conversion
- Dark & Light themes — auto-detects system preference, manual toggle
- 8 languages — English, Czech, German, Croatian, Hungarian, Italian, Slovak, Slovenian
- Keyboard shortcuts — Ctrl+A/C/X/V, Delete, F2, F5, Backspace, Escape
- Right-click context menu with all available actions
- Multi-file selection with checkboxes and Ctrl+A
- Responsive design — works on desktop, tablet, and mobile
- Session-based authentication or simple access keys
- CSRF token protection on every request
- Extension blacklist blocks executable uploads
- Path traversal protection via
realpath()validation - No database required — pure filesystem storage
Caution
The file manager does NOT include built-in authentication. Without protection, anyone with the URL can upload, delete, and modify files on your server. You must secure it before deploying to production. See Security below.
chmod +x deploy.sh && ./deploy.sh.\deploy.ps1composer install --no-dev --optimize-autoloader
npm ci && npm run build
mkdir -p media/source media/thumbs
chmod 755 media/source media/thumbs
# Point your web server document root to the public/ directoryRun File & Image Manager in a Docker container with PHP 8.4-FPM + Nginx. No need to install PHP, Composer, or Node.js on your machine.
# PowerShell
.\docker\build.ps1 -Run
# Bash / Linux / macOS
./docker/build.sh --run
# Docker Compose
docker compose up -dThe app will be available at http://localhost:8080.
Uploaded files are bind-mounted to the local media/ folder, so they persist across container restarts and are directly accessible on the host.
Mount your own config file as read-only:
docker run -p 8080:80 \
-v ./media:/var/www/html/media \
-v ./config/filemanager.php:/var/www/html/config/filemanager.php:ro \
fileimagemanager| Parameter | Value |
|---|---|
upload_max_filesize |
64 MB |
post_max_size |
64 MB |
memory_limit |
256 MB |
max_execution_time |
120 s |
client_max_body_size (Nginx) |
64 MB |
See docker/README.md for full Docker documentation.
Caution
Every item below is critical. Skipping any of them exposes your server to unauthorized file access, uploads, or remote code execution.
Session-based (recommended): Wrap the file manager in your application's auth layer. Start a PHP session and set $_SESSION['authenticated'] = true before including the file manager. The built-in AuthMiddleware checks for an active session.
Access keys (simple alternative): For deployments without a session system:
'use_access_keys' => true,
'access_keys' => ['your-random-secret-key'],Then open ?key=your-random-secret-key.
Enabled by default. The frontend sends X-CSRF-Token with every API request. Do not disable this.
The default ext_blacklist blocks PHP, scripts, executables, and server config files. Never remove entries from it. Extend it if needed.
config/filemanager.php contains sensitive settings.
- Linux:
chmod 640 config/filemanager.php - IIS: Remove read access for anonymous users.
Always serve over HTTPS in production. The .htaccess / web.config files set security headers (X-Content-Type-Options, X-Frame-Options) but cannot enforce HTTPS.
The backend validates all paths using realpath(). Never bypass these checks.
| Component | Version |
|---|---|
| PHP | >= 8.4 (gd, mbstring, json, curl, fileinfo) |
| Composer | 2.x |
| Node.js | >= 20 (build only) |
| npm | >= 10 (build only) |
| Web server | Apache (mod_rewrite), IIS (URL Rewrite), or Nginx |
All options are in config/filemanager.php with inline documentation.
| Option | Default | Description |
|---|---|---|
base_url |
auto-detected | Base URL (e.g. https://example.com) |
upload_dir |
/source/ |
URL path to uploaded files |
thumbs_upload_dir |
/thumbs/ |
URL path to thumbnails |
current_path |
../source/ |
Filesystem path to uploads (relative to public/) |
thumbs_base_path |
../thumbs/ |
Filesystem path to thumbnails |
| Option | Default | Description |
|---|---|---|
MaxSizeUpload |
10 |
Max upload size in MB |
MaxSizeTotal |
false |
Max total folder size in MB (false = unlimited) |
mime_extension_rename |
true |
Auto-rename extensions based on MIME type |
filePermission |
0644 |
Default file permissions (Linux) |
folderPermission |
0755 |
Default folder permissions (Linux) |
| Option | Default | Description |
|---|---|---|
delete_files |
true |
Allow deleting files |
create_folders |
true |
Allow creating folders |
delete_folders |
true |
Allow deleting folders |
upload_files |
true |
Allow uploading files |
rename_files |
true |
Allow renaming files |
rename_folders |
true |
Allow renaming folders |
duplicate_files |
true |
Allow duplicating files |
extract_files |
false |
Allow extracting ZIP/GZ/TAR archives |
copy_cut_files |
true |
Allow copy/cut/paste for files |
copy_cut_dirs |
true |
Allow copy/cut/paste for directories |
chmod_files |
false |
Show chmod dialog for files |
chmod_dirs |
false |
Show chmod dialog for directories |
download_files |
true |
Allow downloading files |
url_upload |
false |
Allow uploading from URL |
edit_text_files |
false |
Allow editing text files |
create_text_files |
false |
Allow creating text files |
| Option | Default | Description |
|---|---|---|
default_language |
en_EN |
Default language code |
default_view |
0 |
Default view (0 = Grid, 1 = List, 2 = Columns) |
dark_mode |
true |
Dark mode support (respects system preference) |
show_sorting_bar |
true |
Show sort bar in list/columns view |
show_filter_buttons |
true |
Show file type filter buttons |
show_language_selection |
true |
Show language selector |
multiple_selection |
true |
Allow multi-file selection |
| Option | Default | Description |
|---|---|---|
image_max_width |
0 |
Max image width (0 = no limit) |
image_max_height |
0 |
Max image height (0 = no limit) |
image_resizing |
false |
Auto-resize uploaded images |
image_resizing_width |
0 |
Resize width |
image_resizing_height |
0 |
Resize height |
image_resizing_mode |
auto |
auto, exact, portrait, landscape, crop |
image_quality_jpeg |
90 |
JPEG quality (0–100) for saves, conversions, and thumbnails |
image_quality_webp |
90 |
WebP quality (0–100) for saves, conversions, and thumbnails |
default_image_format |
'' |
Default format in the upload dialog: '' (don't change), 'jpeg', or 'webp' |
upload_resize_options |
['1000x1000', '1600x1600', '2400x2400'] |
Preset "max size" choices in the upload dialog. Each entry is WxH — the image is resized to fit (aspect ratio preserved). Empty array hides the dropdown. |
image_watermark |
false |
Path to watermark image or false |
image_editor_active |
true |
Enable Filerobot image editor |
| Option | Default |
|---|---|
ext_img |
jpg, jpeg, png, gif, bmp, svg, ico, webp |
ext_file |
doc, docx, xls, xlsx, pdf |
ext_video |
mov, mpeg, m4v, mp4, avi, mpg, wma, flv, webm |
ext_music |
mp3, mpga, m4a, ac3, aiff, mid, ogg, wav |
ext_misc |
zip, rar, gz, tar |
| Option | Default | Description |
|---|---|---|
googledoc_enabled |
false |
Use Google Docs Viewer for office files |
googledoc_file_exts |
doc, docx, xls, xlsx, ppt, pptx, odt, odp, ods |
Extensions to preview via Google |
Note
PDF files are previewed using the native browser PDF viewer (works offline). Google Docs Viewer is used only for office documents and requires the file to be publicly accessible from the internet.
The included TinyMCE plugin (public/tinymce/plugin.js) provides the simplest integration. It automatically registers a toolbar button and sets up file_picker_callback for native image/media/link dialogs.
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/8.1.2/tinymce.min.js"></script>
<script>
tinymce.init({
selector: '#editor',
license_key: 'gpl',
plugins: 'image media link code',
toolbar: 'undo redo | blocks | bold italic | image media link | fileimagemanager | code',
external_plugins: {
fileimagemanager: '/public/tinymce/plugin.js'
},
relative_urls: false
});
</script>Plugin options (all optional):
| Option | Default | Description |
|---|---|---|
fileimagemanager_url |
auto-detected | File manager URL (e.g. /public/) |
fileimagemanager_crossdomain |
false |
Enable cross-domain postMessage mode |
fileimagemanager_title |
File Image Manager |
Dialog title |
Behavior:
- Toolbar button (
fileimagemanager): opens the file manager dialog. Clicking a file inserts it into the editor. - Image/media/link dialogs: the browse button in TinyMCE native dialogs opens the file manager via
file_picker_callback. - Smart insertion: if text or an image is selected in the editor, the chosen file is inserted as a link (
<a>) wrapping the selection. Without selection, images insert as<img>, videos as<video>, audio as<audio>, and other files as<a>links. - Preview: the eye icon on file hover always shows a preview, even in editor mode.
- Relative URLs: absolute URLs from the file manager are automatically converted to relative paths.
If you prefer not to use the plugin, you can integrate manually via file_picker_callback:
tinymce.init({
selector: '#editor',
plugins: 'image media link',
toolbar: 'image media link',
file_picker_types: 'file image media',
file_picker_callback: function (callback, value, meta) {
var url = '/public/?editor=tinymce&popup=1&crossdomain=0';
window.addEventListener('message', function handler(e) {
if (e.data && e.data.sender === 'fileimagemanager') {
window.removeEventListener('message', handler);
callback(e.data.url, { alt: '' });
}
});
tinymce.activeEditor.windowManager.openUrl({
title: 'File Manager',
url: url,
width: window.innerWidth - 20,
height: window.innerHeight - 40
});
},
relative_urls: false
});Uses CKEditor 5 classic build v41.4.2 (last version without mandatory license key for CDN):
<script src="https://cdn.ckeditor.com/ckeditor5/41.4.2/classic/ckeditor.js"></script>
<script>
ClassicEditor
.create(document.querySelector('#editor'), {
removePlugins: ['CKFinder', 'CKFinderUploadAdapter'],
toolbar: ['heading', '|', 'bold', 'italic', 'link', 'bulletedList',
'numberedList', '|', 'outdent', 'indent', '|', 'blockQuote',
'insertTable', 'mediaEmbed', 'undo', 'redo']
})
.then(function (editor) {
// Add a "Browse Server" button
var button = document.createElement('button');
button.textContent = 'Browse Server';
button.addEventListener('click', function () {
var url = '/public/?editor=ckeditor&popup=1&crossdomain=0';
window.addEventListener('message', function handler(e) {
if (e.data && e.data.sender === 'fileimagemanager') {
window.removeEventListener('message', handler);
var fileUrl = e.data.url;
editor.model.change(function (writer) {
var img = writer.createElement('imageBlock', { src: fileUrl });
editor.model.insertContent(img);
});
}
});
window.open(url, 'fim', 'width=1200,height=800');
});
editor.ui.element.parentNode.insertBefore(button, editor.ui.element);
});
</script>CKEDITOR.config.filebrowserBrowseUrl = '/public/?editor=ckeditor&popup=1';
CKEDITOR.config.filebrowserImageBrowseUrl = '/public/?editor=ckeditor&popup=1';Open the file manager in a popup window. Single click shows a preview, double click selects the file:
var popup = window.open(
'/public/?popup=1',
'fileimagemanager',
'width=1200,height=900'
);
window.addEventListener('message', function handler(e) {
if (e.data && e.data.sender === 'fileimagemanager') {
window.removeEventListener('message', handler);
console.log('Selected file:', e.data.url);
popup.close();
}
});All integrations communicate via window.postMessage. The file manager sends:
{ sender: 'fileimagemanager', url: '/source/path/to/file.jpg' }| URL parameter | Description |
|---|---|
popup=1 |
Enable popup/editor mode (single click = preview, double click = select) |
editor=tinymce |
TinyMCE mode (single click = select file immediately) |
editor=ckeditor |
CKEditor mode |
crossdomain=1 |
Use * as postMessage target origin |
field_id=elementId |
Set value of a DOM input element |
callback=functionName |
Call a global function with the URL |
The public/.htaccess handles URL rewriting. Enable mod_rewrite:
a2enmod rewrite && systemctl restart apache2<VirtualHost *:80>
ServerName filemanager.example.com
DocumentRoot /var/www/filemanager/public
<Directory /var/www/filemanager/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>Install the URL Rewrite module. Point the site to public/. The public/web.config handles rewriting.
server {
listen 80;
server_name filemanager.example.com;
root /var/www/filemanager/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}production-server/
├── config/filemanager.php # Edit for your environment
├── lang/*.json # Translation files
├── public/ # <- Web server document root
│ ├── assets/ # Built JS/CSS (npm run build)
│ ├── tinymce/plugin.js # TinyMCE plugin
│ ├── index.php
│ ├── .htaccess
│ └── web.config
├── src/ # PHP backend
├── vendor/ # Composer dependencies (no-dev)
└── composer.json
Do NOT deploy: frontend/, node_modules/, tests/, package*.json, vite.config.ts, tsconfig.json, vitest.config.ts, phpunit.xml, .git/
# Build
composer install --no-dev --optimize-autoloader && composer dump-autoload -o
npm install --legacy-peer-deps && npm run build
# Copy to server
rsync -av --exclude='frontend/' --exclude='node_modules/' --exclude='tests/' \
--exclude='package*.json' --exclude='vite.config.ts' --exclude='tsconfig.json' \
--exclude='vitest.config.ts' --exclude='phpunit.xml' --exclude='.git/' \
./ user@server:/var/www/filemanager/
# On the server
mkdir -p /var/www/source /var/www/thumbs
chown www-data:www-data /var/www/source /var/www/thumbs# Build
composer install --no-dev --optimize-autoloader
npm install --legacy-peer-deps && npm run build
# Copy to IIS
robocopy . C:\inetpub\filemanager /MIR /XD frontend node_modules tests .git `
/XF package*.json vite.config.ts tsconfig.json vitest.config.ts phpunit.xml
# In IIS Manager: create site -> physical path: C:\inetpub\filemanager\public
# Grant IIS_IUSRS Modify permission on source/ and thumbs/'base_url' => 'https://example.com',
'upload_dir' => '/source/',
'current_path' => '../source/',
'thumbs_base_path'=> '../thumbs/',
'use_access_keys' => true,
'access_keys' => ['your-random-secret-key'],
'MaxSizeUpload' => 10,| Problem | Check |
|---|---|
| Blank page | public/assets/.vite/manifest.json exists? |
| 500 error | PHP error log, missing extensions? |
| Autoload error | vendor/autoload.php exists? Run composer install |
| 404 on API | URL Rewrite module installed? (Apache: mod_rewrite, IIS: URL Rewrite) |
| Shortcut | Action |
|---|---|
Ctrl+A |
Select all files |
Ctrl+C |
Copy selected |
Ctrl+X |
Cut selected |
Ctrl+V |
Paste |
Delete |
Delete selected |
F2 |
Rename |
F5 |
Refresh |
Backspace |
Go to parent directory |
Escape |
Close overlay / menu |
en_EN English, cs Cestina, de Deutsch, hr Hrvatski, hu_HU Magyar, it Italiano, sk Slovencina, sl Slovenscina
# Install with dev dependencies
./deploy.sh --dev # Linux/macOS
.\deploy.ps1 -Dev # Windows
# Vite dev server with HMR
npm run dev
# PHP built-in server (separate terminal)
php -S localhost:80 -t public/
# Production build
npm run build
# Type check
npm run lint245 tests across PHPUnit (PHP) and Vitest (frontend).
./test.sh # All tests (Linux/macOS)
.\test.ps1 # All tests (Windows)
./test.sh php # PHP only
./test.sh frontend # Frontend only
npm test # Vitest
php vendor/bin/phpunit # PHPUnitAll endpoints return JSON. Prefix: /api/
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/config/init |
Initialize session, get config + translations |
GET |
/api/config |
Get current config |
GET |
/api/config/languages |
List available languages |
POST |
/api/config/language |
Change language |
POST |
/api/config/view |
Change view mode |
POST |
/api/config/sort |
Change sort field/direction |
POST |
/api/config/filter |
Change type filter |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/files |
List directory contents (paginated) |
GET |
/api/files/info |
Get file info |
GET |
/api/files/download |
Download file (range requests supported) |
GET |
/api/files/preview |
Get preview data |
GET |
/api/files/content |
Get text file content |
GET |
/api/folders/tree |
Get recursive directory tree |
POST |
/api/folders/create |
Create folder |
POST |
/api/folders/rename |
Rename folder |
POST |
/api/folders/delete |
Delete folder |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/operations/rename |
Rename file |
POST |
/api/operations/delete |
Delete file |
POST |
/api/operations/delete-bulk |
Delete multiple files |
POST |
/api/operations/duplicate |
Duplicate file |
POST |
/api/operations/copy |
Copy to clipboard |
POST |
/api/operations/cut |
Cut to clipboard |
POST |
/api/operations/paste |
Paste from clipboard |
POST |
/api/operations/clear-clipboard |
Clear clipboard |
POST |
/api/operations/chmod |
Change permissions |
POST |
/api/operations/extract |
Extract archive |
POST |
/api/operations/save-text |
Save text file |
POST |
/api/operations/create-file |
Create text file |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/upload |
Upload files (multipart) |
POST |
/api/upload/url |
Upload from URL |
POST |
/api/image/save |
Save edited image (base64) |
.
├── config/filemanager.php # Configuration (all options documented)
├── frontend/ # Vue 3 SPA source
│ ├── api/ # Axios API client
│ ├── components/ # Vue components
│ │ ├── common/ # ContextMenu, LoadingOverlay
│ │ ├── dialogs/ # Confirm, Prompt, Alert, Chmod, TextEditor, Language
│ │ ├── file-list/ # FileGrid, FileList, FileColumns, FileItem, FileIconSprite
│ │ ├── layout/ # AppHeader, Breadcrumb, SortBar, StatusBar, DirectorySidebar
│ │ ├── preview/ # ImagePreview, MediaPlayer, TextPreview, ImageEditor
│ │ └── upload/ # UploadPanel, DropZone, UploadProgress, UrlUpload
│ ├── composables/ # useKeyboard, useDragDrop
│ ├── plugins/ # TinyMCE 8 + CKEditor plugins
│ ├── stores/ # Pinia (config, file, ui, clipboard, upload)
│ ├── types/ # TypeScript definitions
│ └── utils/ # Utility functions
├── lang/*.json # Translations (8 languages)
├── public/ # Web server document root
│ ├── assets/ # Built JS/CSS (npm run build)
│ ├── tinymce/plugin.js # TinyMCE plugin
│ ├── index.php # Front controller
│ ├── .htaccess # Apache rewrite
│ └── web.config # IIS rewrite
├── src/ # PHP backend (PSR-4: RFM\)
│ ├── Config/ # AppConfig, ConfigLoader
│ ├── Controller/ # Config, File, Folder, Upload, Operation, Image
│ ├── DTO/ # FileItem, BreadcrumbItem
│ ├── Enum/ # ViewType, SortField, FileCategory, ...
│ ├── Exception/ # Forbidden, PathTraversal, FileNotFound, ...
│ ├── Http/ # Request, JsonResponse, StreamResponse
│ ├── Middleware/ # AuthMiddleware, CsrfMiddleware
│ ├── Service/ # Security, FileSystem, Upload, Thumbnail, Image
│ ├── App.php # Application bootstrap
│ └── Router.php # API routes
├── tests/ # PHPUnit + Vitest
├── media/source/ # Uploaded files (gitignored)
├── media/thumbs/ # Generated thumbnails (gitignored)
├── build.sh / build.ps1 # Build distribution zip
├── deploy.sh / deploy.ps1 # Deploy scripts
└── test.sh / test.ps1 # Test runners
Build a ready-to-deploy .zip archive in the dist/ directory:
./build.sh # Linux / macOS / Git Bash
.\build.ps1 # Windows PowerShellThe script runs the full frontend build, installs production-only Composer dependencies, packages everything needed for deployment into dist/fileimagemanager-v1.0.0.zip, and restores dev dependencies afterwards.
| Layer | Technology |
|---|---|
| Frontend | Vue 3.5, TypeScript, Vite 8, Tailwind CSS 4, Pinia 3.0 |
| Image Editor | Filerobot Image Editor 5.0 |
| Backend | PHP 8.4+, GD library |
| Storage | Filesystem only (no database) |
CC0 1.0 Universal - Public Domain



