PHPackages                             codebyray/livewire-media-uploader - 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. codebyray/livewire-media-uploader

ActiveLibrary

codebyray/livewire-media-uploader
=================================

Reusable Livewire v3 media uploader that integrates with Spatie Media Library and ships a publishable view.

v0.3.1(8mo ago)230↓100%MITBladePHP &gt;=8.1

Since Aug 31Pushed 8mo agoCompare

[ Source](https://github.com/codebyray/livewire-media-uploader)[ Packagist](https://packagist.org/packages/codebyray/livewire-media-uploader)[ RSS](/packages/codebyray-livewire-media-uploader/feed)WikiDiscussions main Synced 1mo ago

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

[![media_uploader_screenshot](https://private-user-images.githubusercontent.com/2058181/484386060-0852d2c2-762a-440c-ad61-8c0b6939f930.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQyNDA3OTksIm5iZiI6MTc3NDI0MDQ5OSwicGF0aCI6Ii8yMDU4MTgxLzQ4NDM4NjA2MC0wODUyZDJjMi03NjJhLTQ0MGMtYWQ2MS04YzBiNjkzOWY5MzAucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDMyMyUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAzMjNUMDQzNDU5WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZTZhMjdlYmY0NDE2YzI3MTFmNTI2YzUyN2ZmNDNkN2Y3NzIwODhjMWRkMDllMGZhYTYwMzFhYTZjZjRmYjk5MiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.brakybrjp2Fz4FQjER_kmF35_iAdR_0UhgKy7HMIfB0)](https://private-user-images.githubusercontent.com/2058181/484386060-0852d2c2-762a-440c-ad61-8c0b6939f930.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQyNDA3OTksIm5iZiI6MTc3NDI0MDQ5OSwicGF0aCI6Ii8yMDU4MTgxLzQ4NDM4NjA2MC0wODUyZDJjMi03NjJhLTQ0MGMtYWQ2MS04YzBiNjkzOWY5MzAucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDMyMyUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAzMjNUMDQzNDU5WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZTZhMjdlYmY0NDE2YzI3MTFmNTI2YzUyN2ZmNDNkN2Y3NzIwODhjMWRkMDllMGZhYTYwMzFhYTZjZjRmYjk5MiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.brakybrjp2Fz4FQjER_kmF35_iAdR_0UhgKy7HMIfB0)Livewire Media Uploader
=======================

[](#livewire-media-uploader)

Livewire Media Uploader is a reusable Livewire v3 component that integrates seamlessly with Spatie Laravel Media Library. It ships a clean Tailwind Blade view by default (fully publishable), Bootstrap theme as an option, Alpine overlays for previews/confirmations, drag-and-drop uploads, per-file metadata (caption/description/order), configurable presets, name-conflict strategies, and optional SHA-256 duplicate detection. Drop it in, point it at a model, and you’re shipping in minutes.

---

Table of Contents
-----------------

[](#table-of-contents)

- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Publishing Assets](#publishing-assets)
- [Theme System](#theme-system-tailwind--bootstrap--custom)
    - [Dark Mode - Tailwind](#dark-mode-tailwind-theme)
    - [Custom Theme](#custom-themes)
- [Quick Start](#quick-start)
- [Usage Examples](#usage-examples)
    - [Create flow (deferred uploads)](#create-flow-deferred-uploads)
- [Configuration](#configuration)
- [Props](#props)
- [Events](#events)
- [Model Setup (Spatie Media Library)](#model-setup-spatie-media-library)
- [Overlays &amp; UX Notes](#overlays--ux-notes)
- [Troubleshooting](#troubleshooting)
- [Roadmap](#roadmap)
- [License](#license)

---

Features
--------

[](#features)

- ✅ Livewire v3 component with themeable Blade UI
    - Tailwind (default)
    - Bootstrap (optional)
    - Fully publishable and overridable
- ✅ Spatie Media Library integration (attach, list, edit meta, delete)
- ✅ **Publishable view** for per-project customization
- ✅ Drag &amp; drop uploads + progress bar
- ✅ Inline edit of **caption / description / order**
- ✅ Name-conflict strategies: **rename | replace | skip | allow**
- ✅ Optional **exact duplicate** detection via SHA-256
- ✅ Collection → preset mapping (auto `accept` attribute)
- ✅ Image preview **overlay** + delete confirmation **modal**
- ✅ Works with:
    - Saved model instance (`:for="$model"`)
    - String model + id (`model="user" :id="1"`)
    - FQCN, morph map alias, or dotted paths with custom namespaces
    - Local alias map

---

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

[](#requirements)

- PHP **8.1+**
- Laravel **10.x | 11.x | 12.x**
- Livewire **^3.0**
- spatie/laravel-medialibrary **^10.12**
- TailwindCSS (optional but recommended for the default view)
- Alpine.js (used by overlays/progress; see [Overlays &amp; UX Notes](#overlays--ux-notes))
- CSS depending on theme:
    - Tailwind theme → TailwindCSS (recommended)
    - Bootstrap theme → Bootstrap CSS (no Bootstrap JS required; Alpine drives modals)

---

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

[](#installation)

```
composer require codebyray/livewire-media-uploader
```

Auto-discovery will register the service provider. If you disable discovery, add:

```
// config/app.php
'providers' => [
    // ...
    Codebyray\LivewireMediaUploader\MediaUploaderServiceProvider::class,
],
```

The component is registered under **both** aliases:

- ``
- ``

---

Publishing Assets
-----------------

[](#publishing-assets)

### Config:

[](#config)

```
php artisan vendor:publish --tag=media-uploader-config
```

### Views:

[](#views)

```
php artisan vendor:publish --tag=media-uploader-views
```

After publishing, customize the Blade at:

```
resources/views/vendor/media-uploader/themes/tailwind/media-uploader.blade.php
resources/views/vendor/media-uploader/themes/bootstrap/media-uploader.blade.php
```

Theme System (Tailwind + Bootstrap + custom)
--------------------------------------------

[](#theme-system-tailwind--bootstrap--custom)

Select the theme in config/media-uploader.php:

```
// config/media-uploader.php
return [
    'theme'  => 'tailwind', // 'tailwind' (default) or 'bootstrap'
    'themes' => [
        'tailwind'  => 'media-uploader::themes.tailwind.media-uploader',
        'bootstrap' => 'media-uploader::themes.bootstrap.media-uploader',
    ],
    // ...
];
```

### Dark mode (Tailwind theme)

[](#dark-mode-tailwind-theme)

This package’s Tailwind theme is dark-ready. Add this tiny snippet in your main layout `` to apply the user’s saved choice / system default:

```

    (() => {
        const t = localStorage.theme ?? 'system';
        const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
        const dark = t === 'dark' || (t === 'system' && prefersDark);
        if (dark) document.documentElement.classList.add('dark');
    })();

```

### Custom themes

[](#custom-themes)

- Copy an existing theme directory (e.g. themes/tailwind) to themes/custom and edit the Blade.
- Register it in the map and select it: ```
    'theme'  => 'custom',
    'themes' => [
        'tailwind'  => 'media-uploader::themes.tailwind.media-uploader',
        'bootstrap' => 'media-uploader::themes.bootstrap.media-uploader',
        'custom'    => 'media-uploader::themes.custom.media-uploader',
    ],
    ```

    > Note: The component’s Livewire + Alpine behavior is identical across themes. Only classes/markup differ. If you use the Bootstrap theme, make sure your layout includes Bootstrap CSS.

Environment variables (optional)
--------------------------------

[](#environment-variables-optional)

You can override preset limits and accepted types/mimes via .env. These map directly to config/media-uploader.php:

```
# Livewire Media Uploader (optional)

# Images
MEDIA_TYPES_IMAGES=jpg,jpeg,png,webp,avif,gif
MEDIA_MIMES_IMAGES=image/jpeg,image/png,image/webp,image/avif,image/gif
MEDIA_MAXKB_IMAGES=10240

# Documents
MEDIA_TYPES_DOCS=pdf,doc,docx,xls,xlsx,ppt,pptx,txt
MEDIA_MIMES_DOCS=application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,text/plain
MEDIA_MAXKB_DOCS=20480

# Videos
MEDIA_TYPES_VIDEOS=mp4,mov,webm
MEDIA_MIMES_VIDEOS=video/mp4,video/quicktime,video/webm
MEDIA_MAXKB_VIDEOS=102400

# Fallback preset
MEDIA_TYPES_DEFAULT=jpg,jpeg,png,webp,avif,gif,pdf,doc,docx,xls,xlsx,ppt,pptx,txt
MEDIA_MIMES_DEFAULT=image/jpeg,image/png,image/webp,image/avif,image/gif,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,text/plain
MEDIA_MAXKB_DEFAULT=10240
```

### Notes

[](#notes)

- Values are comma-separated; spaces are OK (the package trims them).
- After changing .env, run:

```
- php artisan config:clear
# (or) php artisan config:cache
```

- The `` attribute is auto-filled from the active preset when accept\_from\_config is true (default). You can still override it per-component with the accept prop.
- If uploads fail due to size, make sure your PHP/Server limits also allow it (e.g. upload\_max\_filesize, post\_max\_size).

---

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

[](#quick-start)

1. Ensure your target Eloquent model implements `Spatie\MediaLibrary\HasMedia` and is **saved**.

    #### Model Setup (Spatie Media Library)

    [](#model-setup-spatie-media-library)

    Your model must implement `HasMedia` and be **saved** before attaching media.

    ```
    use Spatie\MediaLibrary\HasMedia;
    use Spatie\MediaLibrary\InteractsWithMedia;
    use Spatie\MediaLibrary\MediaCollections\Models\Media;

    class Post extends Model implements HasMedia
    {
        use InteractsWithMedia;

        public function registerMediaCollections(): void
        {
            $this->addMediaCollection('photos')        // matches collection="photos"
                ->useDisk('public')                    // or 's3'
                ->withResponsiveImages();             // optional

            // If you also have avatars somewhere:
            $this->addMediaCollection('avatars')->singleFile();
        }

        public function registerMediaConversions(Media $media = null): void
        {
            $this->addMediaConversion('thumb')
                ->fit('contain', 256, 256)
                ->performOnCollections('photos', 'avatars') // scope to specific collections
                ->nonQueued();
        }
    }
    ```
2. Include Livewire &amp; Alpine (usually in your app layout):

    ```
    @livewireStyles
    [x-cloak]{ display:none !important; }
    @livewireScripts
    ```
3. Drop the component into your Blade:

    ```

    ```

---

Usage Examples
--------------

[](#usage-examples)

1. Pass a saved model instance

    ```

    ```
2. Short string model + id

    ```

    ```
3. Morph map alias\*\*

    ```

    ```
4. FQCN

    ```

    ```
5. Dotted path + custom namespaces

    ```

    ```
6. Local aliases (per-instance)

    ```

    ```
7. Single-file mode + hide list

    ```

    ```
8. Name conflict strategies

    ```

    ```
9. Duplicate detection by SHA-256

    ```

    ```
10. Restrict types/mimes/max size manually

    ```

    ```

### Create flow (deferred uploads)

[](#create-flow-deferred-uploads)

You can let users pick files **before** the model exists, and attach them **after** save.

**Blade (create page)**

```

```

#### Livewire component (simplified)

[](#livewire-component-simplified)

```
use App\Models\Post;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\On;
use Livewire\Component;

class PostCreate extends Component
{
    public string $title = '';
    public string $body  = '';
    public ?int $pendingPostId = null;

    protected function rules(): array
    {
        return ['title' => 'required|string|max:255', 'body' => 'required|string'];
    }

    public function save(): void
    {
        $post = Post::create([
            'user_id' => Auth::id(),
            'title'   => $this->title,
            'body'    => $this->body,
        ]);

        // Let uploaders attach everything queued for this collection
        $this->pendingPostId = $post->id;

        // Fire once per collection rendered on the page
        $this->dispatch('media:attach', model: 'post', id: $post->id, collection: 'images');
    }

    #[On('media-attached')]
    public function afterMediaAttached(string $model, string|int $id): void
    {
        if ($this->pendingPostId && (int)$id === (int)$this->pendingPostId) {
            $this->pendingPostId = null;
            $this->redirectRoute('posts.show', ['post' => $id], navigate: true);
        }
    }

    public function render() { return view('livewire.posts.post-create'); }
}
```

#### How it works

[](#how-it-works)

- On create screens, the component accepts model="post" without an id.
- Files and per-file metadata are queued locally.
- After you persist the model, dispatch: ```
    $this->dispatch('media:attach', model: 'post', id: $post->id, collection: 'images');
    ```
- The uploader resolves the saved target, attaches any queued files, and emits media-attached.

---

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

[](#configuration)

The package merges `config/media-uploader.php`:

- `accept_from_config` — if `true`, auto-fills `` from the selected preset
- `collections` — map collection name → preset key
- `presets.*.types` — extensions (comma-separated)
- `presets.*.mimes` — MIME types (comma-separated)
- `presets.*.max_kb` — max file size per file in KB

Example:

```
'collections' => [
    'avatars'     => 'images',
    'images'      => 'images',
    'attachments' => 'docs',
],
```

Show all collections together (grouped) Set `:list-all="true"` to render a grouped list of **every collection** on the target model. Items stay fully editable.

```

```

The component decides the active preset in this order:

1. Explicit `$preset` prop
2. Mapping from `collections`
3. Fallback to `default`

---

Props
-----

[](#props)

PropTypeDefaultDescription`for``Model`—Saved Eloquent model instance implementing `HasMedia`.`model``string`—Model resolver: alias, FQCN, morph alias, or dotted path.`id``intstring`—`collection``string``images`Media collection name.`disk``?string``null`Storage disk (e.g. `s3`).`multiple``bool``true`Toggle multi-file input.`accept``?string``null``` override (otherwise may be auto from config).`showList``bool``true`Show the attached media list.`maxSizeKb``int``500` (overridden to preset’s `max_kb` if empty)Max file size (KB).`preset``?string``null`Choose a preset (`images`, `docs`, `videos`, `default`, etc.).`allowedTypes``array``[]`Extensions filter (e.g. `['jpg','png']`).`allowedMimes``array``[]`MIME filter (e.g. `['image/jpeg']`).`onNameConflict``string``rename`Strategy: `rename` | `replace` | `skip` | `allow`.`skipExactDuplicates``bool``false`Uses SHA-256 stored in `custom_properties->sha256`.`namespaces``array``['App\\Models']`Namespaces for dotted-path resolution.`aliases``array``[]`Local alias map, e.g. `['profile' => \App\Models\User::class]`.`attachedFilesTitle``string``"Current gallery"`Heading text in the list card.`listAll``bool``false`When `true`, the attached media list shows **all collections**, grouped by collection name (still editable).---

Events
------

[](#events)

The component dispatches browser events you can listen for:

- `media:attach` — **incoming** event the component listens for. Arguments: `model` (class/alias), `id`, optional `collection`, optional `disk`. Triggers attaching of any queued files to the now-saved target.
- `media-attached` — emitted after a successful `media:attach`. Payload: `{ model: FQCN, id: string }`.
- `media-uploaded` — emitted after an immediate upload (when a target already exists).
- `media-deleted` — emitted after deletion (`detail.id` contains the Media ID).
- `media-meta-updated` — emitted after inline metadata is saved.

Example:

```

```

---

> The list view tries `getUrl('thumb')` and falls back to `getUrl()` if no conversion is available.

---

Overlays &amp; UX Notes
-----------------------

[](#overlays--ux-notes)

- **Image Preview Overlay** (lightbox): toggled by `x-show="preview.open"`.
- **Delete Confirmation Modal**: toggled by `$wire.confirmingDeleteId !== null`.
- Add once in your layout to prevent flash-of-overlay: ```
    [x-cloak]{ display:none !important; }
    ```
- Z-index defaults: preview `z-[60]`, delete modal `z-50`. Adjust to your stack if you have higher layers.

---

Troubleshooting
---------------

[](#troubleshooting)

- **“Target model must be saved…”**
    Ensure the model exists in DB (`$model->exists === true`) before rendering the component.
- **“must implement Spatie\\MediaLibrary\\HasMedia”**
    Add `implements HasMedia` + `InteractsWithMedia` to your model.
- **Unknown model class/alias**
    If using `model="something"` + `:id`, make sure:

    - It’s a valid FQCN, morph alias, or maps via dotted path within `namespaces`, or
    - You passed a local alias via `:aliases="['something' => \App\Models\YourModel::class]`.
- **`accept` not applied**
    Set `accept_from_config=true` and ensure your preset has `types`/`mimes`. Or override via `accept` prop.
- **No thumbnails**
    Add a `thumb` conversion (see [Model Setup](#model-setup-spatie-media-library)).

---

Roadmap
-------

[](#roadmap)

- Drag-to-reorder (update `order_column`).
- Show document icon instead of thumbnail in Attached media list if the file is not an image.

PRs welcome!

---

License
-------

[](#license)

**MIT** © CodebyRay (Ray Cuzzart II)

---

**Component aliases:** `media-uploader` and `media.media-uploader`
**View namespace:** `media-uploader::livewire.media-uploader`

###  Health Score

32

—

LowBetter than 71% of packages

Maintenance64

Regular maintenance activity

Popularity12

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity37

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% 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

Every ~0 days

Total

4

Last Release

249d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/56b9f090994212c557bf9e64ab54e367eacc9f614b152be8ff532cc7f3f2565b?d=identicon)[codebyray](/maintainers/codebyray)

---

Top Contributors

[![codebyray](https://avatars.githubusercontent.com/u/2058181?v=4)](https://github.com/codebyray "codebyray (14 commits)")

---

Tags

alpinejscomponentfile-uploadslaravellivewiremedia-libraryspatiespatie-laravel-medialibrarytailwindcssuploaderspatielaravellivewirefile uploadscomponentmedia librarytailwindcssalpinejsuploaderSpatie Laravel MediaLibrary

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/codebyray-livewire-media-uploader/health.svg)

```
[![Health](https://phpackages.com/badges/codebyray-livewire-media-uploader/health.svg)](https://phpackages.com/packages/codebyray-livewire-media-uploader)
```

###  Alternatives

[usernotnull/tall-toasts

A Toast notification library for the Laravel TALL stack. You can push notifications from the backend or frontend to render customizable toasts with almost zero footprint on the published CSS/JS!

570396.2k4](/packages/usernotnull-tall-toasts)[lakm/laravel-comments

Integrate seamless commenting functionality into your Laravel project.

40012.9k1](/packages/lakm-laravel-comments)

PHPackages © 2026

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