PHPackages                             anil/file-picker - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [File &amp; Storage](/categories/file-storage)
4. /
5. anil/file-picker

ActiveLibrary[File &amp; Storage](/categories/file-storage)

anil/file-picker
================

A powerful media library and file picker component for Laravel Livewire. Supports images, videos, audio, documents and all file types with a beautiful modal interface.

v0.1.0(1mo ago)422[2 PRs](https://github.com/anilkumarthakur60/livewire-file-picker/pulls)MITPHPPHP ^8.2|^8.3|^8.4|^8.5CI passing

Since May 28Pushed 4w ago1 watchersCompare

[ Source](https://github.com/anilkumarthakur60/livewire-file-picker)[ Packagist](https://packagist.org/packages/anil/file-picker)[ RSS](/packages/anil-file-picker/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (9)Versions (5)Used By (0)

File Picker
===========

[](#file-picker)

A powerful media library and file picker component for Laravel Livewire. Supports images, videos, audio, documents, and every other file type, all behind a polished modal interface.

[![File Picker screenshot](img.png)](img.png)

Features
--------

[](#features)

- 📁 **All file types** — images, videos, audio, documents, spreadsheets, presentations, archives, code
- 🎨 **Clean, themeable UI** — fully recolorable through config, with a responsive sheet-style detail panel on tablet/mobile
- 🔍 **Search &amp; filter** — by file type, folder, tag, or favorite
- 📤 **Drag &amp; drop + paste** — drop files in, or paste straight from the clipboard
- 🚨 **Upload error reporting** — per-file validation messages, browser-side failures (413/network) surface in the same toast
- ✅ **Single / multiple selection** — with a configurable `max_files`
- 🗑️ **Trash &amp; restore** — soft-delete with retention-based pruning
- 🔁 **Replace file** — swap the file behind a media row without changing its ID
- 🪪 **Hash &amp; duplicate detection** — SHA-256 dedup with `reuse`, `reject`, or `allow` strategies
- ⭐ **Favorites, 🏷️ tags, 📂 folders** — organize without inventing your own taxonomy
- ✏️ **Inline editing** — rename and edit alt text without leaving the picker
- 👤 **Ownership tracking** — auto-record `user_id`, optionally scope per user
- 📊 **Storage quotas** — global and per-user
- 📈 **Statistics API** — counts, sizes, by-type via `FilePicker::getStats()`
- 📥 **Downloads** — single files or bulk ZIP
- 🛠️ **Console commands** — `file-picker:prune-trash`, `file-picker:prune-orphans`, `file-picker:stats`
- 🎯 **Form integration** — Livewire components and traditional HTML forms
- ♿ **Accessible** — keyboard nav, focus management, Esc to close

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 11.x, 12.x, or 13.x
- Livewire 3.x or 4.x
- [`plank/laravel-mediable`](https://github.com/plank/laravel-mediable) ^6.0 — installed automatically

Installation
------------

[](#installation)

```
composer require anil/file-picker
php artisan file-picker:install
```

`file-picker:install` publishes `config/file-picker.php` and runs an **additive** migration that adds the columns the package needs (`folder`, `tags`, `is_favorite`, `hash`, `width`, `height`, `duration`, `user_id`, `download_count`, `custom_properties`, `deleted_at`) to Plank's existing `media` table. CSS/JS are served via a built-in route — nothing to publish, nothing to compile.

### Install command flags

[](#install-command-flags)

```
php artisan file-picker:install --force         # overwrite already-published files
php artisan file-picker:install --no-migrate    # publish only, skip migrations
php artisan file-picker:install --views         # publish blade views for UI overrides
php artisan file-picker:install --lang          # publish language files
php artisan file-picker:install --assets        # publish CSS/JS to public/ (optional)
```

### Add stack slots to your layout

[](#add-stack-slots-to-your-layout)

The component pushes CSS to `@stack('head')` and JS to `@stack('scripts')`:

```

    @stack('head')

    {{ $slot }}
    @stack('scripts')

```

Quick Start
-----------

[](#quick-start)

```
{{-- Single file --}}

{{-- Multiple files, up to 5 --}}

{{-- Restrict to specific file types --}}

```

Usage
-----

[](#usage)

### In a Livewire component

[](#in-a-livewire-component)

```
namespace App\Livewire;

use Livewire\Attributes\On;
use Livewire\Component;

class PostForm extends Component
{
    public array $selectedMedia = [];

    #[On('filesSelected')]
    public function handleFilesSelected(array $selected, string $inputName): void
    {
        // $selected is an array of media IDs
        $this->selectedMedia = $selected;
    }

    public function render()
    {
        return view('components.post-form');
    }
}
```

```

    Selected: {{ count($selectedMedia) }} files

```

### In a traditional HTML form

[](#in-a-traditional-html-form)

```

    @csrf

    {{-- Single file — populates a hidden input --}}

    {{-- Multiple files — auto-submit form after selection --}}

    Save

```

### With a JavaScript callback

[](#with-a-javascript-callback)

```

function onMediaSelected(selected, inputName, inputId) {
    console.log('Selected media:', selected);
}

```

Component Properties
--------------------

[](#component-properties)

PropertyTypeDefaultDescription`multiple``bool``false`Allow multiple selection`maxFiles``int``10`Maximum number of files that can be selected`selected``array``[]`Pre-selected media IDs`allowedTypes``array``[]`Restrict to specific file types (empty = all)`inputName``string``'files'`Name for the hidden input(s)`inputId``string`autoID for the hidden input`formId``string``''`Form ID to target for auto-submit`autoSubmit``bool``false`Auto-submit the form after selection`callbackFunction``string``''`Global JS function name called after selection`buttonLabel``string`autoOverride the trigger button label`showPreview``bool``true`Show selected file previews below the button`perPage``int``24`Items per page in the media libraryAllowed File Types
------------------

[](#allowed-file-types)

Restrict the picker via `allowedTypes`:

```

```

TypeExtensions`image`jpg, jpeg, png, gif, webp, svg, bmp, ico, tiff, avif`video`mp4, webm, ogg, mov, avi, mkv, wmv, flv, m4v`audio`mp3, wav, aac, ogg, flac, m4a, wma, aiff`document`pdf, doc, docx, txt, rtf, odt, md, epub`spreadsheet`xls, xlsx, csv, ods, numbers`presentation`ppt, pptx, odp, key`archive`zip, rar, 7z, tar, gz, bz2, xz`code`js, ts, php, html, css, json, yaml, vue, jsx, tsx, py, go, rs, etc.Extensions per type can be customised in `config/file-picker.php` under `extensions`.

Events
------

[](#events)

### JavaScript event

[](#javascript-event)

Fired on `window` after the user confirms a selection:

```
window.addEventListener('file-picker:selected', (event) => {
    const { selected, inputName, inputId } = event.detail;
    console.log('Selected media:', selected);
});
```

Each item in `selected` is an object with `id`, `url`, `filename`, `size`, `extension`, `file_type`, `alt`, `created_at`.

### Livewire events

[](#livewire-events)

Two events fire whenever the selection changes — pick the one that fits your handler shape:

**`filesSelected`** — named arguments, easiest for typed signatures:

```
#[On('filesSelected')]
public function onFilesSelected(array $selected, string $inputName): void
{
    // $selected is an array of media IDs (int)
    // $inputName is the picker's `input-name` prop
    $this->selectedIds = $selected;
}
```

**`file-picker-selected`** — single array payload with the full picker context:

```
#[On('file-picker-selected')]
public function onFilePickerSelected(array $payload): void
{
    // keys: selected, inputName, inputId, formId, multiple, autoSubmit, callbackFunction
    $this->selectedIds = $payload['selected'];
}
```

> If you have several pickers on one page, switch on `$inputName` to route the selection to the right property.

Upload Errors
-------------

[](#upload-errors)

Upload problems are surfaced to the UI at three levels:

1. **Server-side validation** (size, mime type) — failures render as a toast plus a per-file list (`{filename}: {message}`).
2. **Per-file driver failures** — `DuplicateMediaException`, `StorageQuotaExceededException`, `UploadFailedException`, or any other `Throwable` thrown by the driver are aggregated into the toast (e.g. *"2 uploaded, 1 failed"*).
3. **Browser-side failures** — the bundled JS listens for `livewire-upload-error` and forwards the HTTP status:
    - `413` → *"the file is larger than the server allows"*
    - `422` → *"the file did not pass validation"*
    - other status codes → generic *"Upload failed (HTTP …)"*

Error toasts are sticky — dismiss with the `×` button or any new upload action.

To push your own error into the toast (e.g. from a custom driver):

```
$this->setUploadError('Quota exceeded — contact your administrator.');
```

Tablet &amp; Mobile
-------------------

[](#tablet--mobile)

The library tab uses a side-by-side layout on desktop (≥1025px) with the **Attachment Details** panel always visible. On tablet/mobile (≤1024px), the panel becomes a right-side sheet that's closed by default — tapping a thumbnail just selects it.

To open the details sheet on touch devices, tap the **edit icon** that sits next to the selection checkbox on each item. It promotes the item to the active selection (without toggling existing selections off) and slides the sheet in. The icon is hidden on desktop where the sidebar is always inline. Rename it via the `texts.view_details` config key (or the published lang file).

Drivers
-------

[](#drivers)

### Plank driver (default)

[](#plank-driver-default)

Built on top of [`plank/laravel-mediable`](https://github.com/plank/laravel-mediable) — installed automatically. The bundled `FilePickerMedia` model extends Plank's `Media` and the install migration adds the extra columns to Plank's existing `media` table.

```
FILE_PICKER_DRIVER=plank
FILE_PICKER_DISK=public
FILE_PICKER_DIRECTORY=media
```

> **Using a non-public disk?** Plank's `mediable.allowed_disks` config defaults to `['public']`. Publish Plank's config (`php artisan vendor:publish --tag=mediable-config`) and add your disk to `allowed_disks`.

### Custom driver

[](#custom-driver)

Implement `Anil\LivewireFilePicker\Contracts\MediaDriverInterface` (or extend `Anil\LivewireFilePicker\Drivers\AbstractDriver`) and register the FQCN:

```
// config/file-picker.php
'driver' => \App\Media\MyCustomDriver::class,
```

Authorization
-------------

[](#authorization)

Default is "everything allowed." For real apps, plug in an authorization class:

```
namespace App\Auth;

use Anil\LivewireFilePicker\Contracts\FilePickerAuthorizationInterface;

class MediaAuthorization implements FilePickerAuthorizationInterface
{
    public function canViewLibrary(): bool         { return auth()->check(); }
    public function canUpload(): bool              { return auth()->user()?->can('upload-media') ?? false; }
    public function canDelete(int $mediaId): bool  { return auth()->user()?->can('delete-media') ?? false; }
    public function canEditAlt(int $mediaId): bool { return auth()->check(); }
}
```

```
// config/file-picker.php
'authorization_class' => \App\Auth\MediaAuthorization::class,
```

Custom Filters
--------------

[](#custom-filters)

Add filter controls to the library toolbar in two parts:

**1. UI controls in config:**

```
'ui' => [
    'custom_filters' => [
        [
            'name'        => 'tag',
            'label'       => 'Tag',
            'type'        => 'select',          // select | text | checkbox | date_range
            'placeholder' => 'All Tags',
            'options'     => ['' => 'All Tags', 'nature' => 'Nature', 'urban' => 'Urban'],
        ],
        [
            'name'  => 'featured',
            'label' => 'Featured Only',
            'type'  => 'checkbox',
        ],
    ],
    'custom_filter_class' => \App\Filters\MediaFilter::class,
],
```

**2. The filter class:**

```
namespace App\Filters;

use Anil\LivewireFilePicker\Contracts\CustomFilter;
use Illuminate\Database\Eloquent\Builder;

class MediaFilter implements CustomFilter
{
    public function apply(Builder $query, array $filters): Builder
    {
        if (!empty($filters['tag']))      $query->where('tag', $filters['tag']);
        if (!empty($filters['featured'])) $query->where('featured', true);

        return $query;
    }
}
```

Configuration
-------------

[](#configuration)

Publish the config to customise everything:

```
php artisan vendor:publish --tag=file-picker-config
```

Key sections:

```
'driver' => env('FILE_PICKER_DRIVER', 'plank'),  // 'plank' | CustomDriver::class

'drivers' => [
    'plank' => [
        'model'      => FilePickerMedia::class,
        'disk'       => env('FILE_PICKER_DISK', 'public'),
        'directory'  => env('FILE_PICKER_DIRECTORY', 'media'),
        'visibility' => env('FILE_PICKER_VISIBILITY', 'public'),
    ],
],

'max_file_size' => env('FILE_PICKER_MAX_SIZE', 102400), // KB (default: 100 MB)

'defaults' => [
    'multiple'     => false,
    'max_files'    => 40,
    'per_page'     => 24,
    'show_preview' => true,
],

'sorting' => [
    'field'     => 'created_at', // created_at | filename | size | extension
    'direction' => 'desc',
],

'features' => [
    'upload'              => true,
    'delete'              => true,
    'bulk_delete'         => true,
    'edit_alt'            => true,
    'rename'              => true,
    'search'              => true,
    'filter'              => true,
    'sorting'             => true,
    'drag_drop'           => true,
    'refresh'             => true,
    'keyboard_navigation' => true,
    'paste_upload'        => true,
],

'ui' => [
    'modal_style'        => 'fullscreen',         // 'fullscreen' | 'centered'
    'thumbnail_height'   => 150,
    'show_type_badges'   => true,
    'show_file_size'     => true,
    'show_date'          => true,

    'colors' => [
        'primary'       => '#0073aa',
        'primary_hover' => '#005a87',
        'danger'        => '#ef4444',
        'success'       => '#10b981',
        'warning'       => '#f59e0b',
    ],

    'font_family'        => "'Inter', sans-serif",
    'border_radius'      => 8,
    'grid_min_width'     => 160,
    'grid_gap'           => 14,
    'sidebar_width'      => 300,
    'backdrop_blur'      => 12,
    'backdrop_opacity'   => 0.6,
    'z_index'            => 9999,

    'filter_types'        => ['image', 'document', 'video', 'audio', 'spreadsheet', 'presentation'],
    'custom_filters'      => [],
    'custom_filter_class' => '',
],

'route_middleware' => ['web'],
```

### Text strings

[](#text-strings)

All UI text is configurable. Publish lang files to translate:

```
php artisan file-picker:install --lang
```

```
'texts' => [
    'modal_title'        => 'Media Library',
    'tab_upload'         => 'Upload Files',
    'tab_library'        => 'Media Library',
    'drop_zone'          => 'Drop files here or click to upload',
    'search_placeholder' => 'Search media...',
    'no_items'           => 'No media found',
    'insert_button'      => 'Insert Selected',
    'delete_confirm'     => 'Are you sure you want to delete this file?',
    'view_details'       => 'View details',     // tablet/mobile edit-icon label
    'sidebar_title'      => 'Attachment Details',
    'close_details'      => 'Close details',
    // ... see config/file-picker.php for the full list
],
```

Customising Views
-----------------

[](#customising-views)

Publish blade views to override the UI:

```
php artisan file-picker:install --views
```

Views are published to `resources/views/vendor/file-picker/`.

API Reference
-------------

[](#api-reference)

### Component methods

[](#component-methods)

MethodDescription`openModal()` / `closeModal()`Open / close the modal`setViewMode('library'|'trash')`Switch between active library and trash`toggleSelection($id)`Toggle selection of a media item`viewDetails($id)`Promote item to active and open details panel`clearSelection()`Clear all selected items`insertSelected()`Confirm selection and close modal`uploadFiles()`Upload pending files`setUploadError($message)`Push an error message into the upload toast`deleteMedia($id)`Soft-delete (move to trash)`restoreMedia($id)`Restore from trash`forceDeleteMedia($id)`Permanently delete (and remove file from disk)`bulkDelete($ids)`Soft-delete many at once`toggleFavorite($id)`Toggle favorite`addTag()` / `removeTag($id, $tag)`Manage tags`startMoving($id)` + `saveMove()`Move to a folder`bulkMoveToFolder($ids, $folder)`Move many at once`startReplacing($id)`Replace the underlying file`refreshMedia()`Reload media items`clearFilters()`Reset search / type / folder / tag / favorite### Facade

[](#facade)

```
use Anil\LivewireFilePicker\Facades\FilePicker;

FilePicker::upload($temporaryFile, ['folder' => 'reports', 'tags' => ['q1']]);
FilePicker::replaceFile($id, $newFile);
FilePicker::toggleFavorite($id);
FilePicker::addTag($id, 'archive-2024');
FilePicker::moveToFolder($id, 'archive/2024');
FilePicker::restore($id);
FilePicker::forceDelete($id);
FilePicker::getStats();          // counts, sizes, by_type, favorites_count, trashed_count
FilePicker::findByHash($sha256); // dedup lookups
```

### Console commands

[](#console-commands)

```
php artisan file-picker:prune-trash --days=30 --dry-run
php artisan file-picker:prune-orphans --dry-run
php artisan file-picker:stats
```

### Download routes

[](#download-routes)

RoutePurpose`GET /file-picker/download/{id}`Force-download a single file`GET /file-picker/download-zip?ids[]=`Stream a zip of selected media### Computed properties

[](#computed-properties)

PropertyTypeDescription`selectedMediaItems``array`Full details of selected media`hasSelection``bool`Whether any items are selected`selectionLabel``string`Human-readable selection count`selectedCount``int`Number of selected itemsLicense
-------

[](#license)

The MIT License (MIT). See [License File](LICENSE.md).

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance92

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity44

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 95.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

57d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/16583802?v=4)[Er. Anil Kumar Thakur](/maintainers/anilkumarthakur60)[@anilkumarthakur60](https://github.com/anilkumarthakur60)

---

Top Contributors

[![anilkumarthakur60](https://avatars.githubusercontent.com/u/16583802?v=4)](https://github.com/anilkumarthakur60 "anilkumarthakur60 (44 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")

---

Tags

laravellivewiremedia libraryfile-uploadimage-pickerMedia Managerfile-picker

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/anil-file-picker/health.svg)

```
[![Health](https://phpackages.com/badges/anil-file-picker/health.svg)](https://phpackages.com/packages/anil-file-picker)
```

###  Alternatives

[tallstackui/tallstackui

TallStackUI is a powerful suite of Blade components that elevate your workflow of Livewire applications.

728176.2k14](/packages/tallstackui-tallstackui)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M136](/packages/laravel-pulse)[moonshine/moonshine

Laravel administration panel

1.3k253.1k86](/packages/moonshine-moonshine)[livewire/flux

The official UI component library for Livewire.

9577.8M138](/packages/livewire-flux)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9762.4M133](/packages/roots-acorn)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
