PHPackages                             waad/media - 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. [Image &amp; Media](/categories/media)
4. /
5. waad/media

ActiveLibrary[Image &amp; Media](/categories/media)

waad/media
==========

Add media to your Laravel application in one place

v4.2.0(1w ago)181621MITPHPPHP ^8.1CI passing

Since Dec 20Pushed 3mo ago2 watchersCompare

[ Source](https://github.com/waadmawlood/media)[ Packagist](https://packagist.org/packages/waad/media)[ Docs](https://github.com/waadmawlood/media)[ RSS](/packages/waad-media/feed)WikiDiscussions main Synced today

READMEChangelog (8)Dependencies (10)Versions (14)Used By (1)

[![Logo](lm.jpg)](lm.jpg)

[![Total Downloads](https://camo.githubusercontent.com/cba0c6073166c1167c1c7be0524094d04eefcb847fdec9a588e003554652a6ca/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f776161642f6d65646961)](https://packagist.org/packages/waad/media)[![Latest Stable Version](https://camo.githubusercontent.com/65367f16578d8fef39f4d1065144f1ced80c63fd7d672ecfed59ccb1ae172750/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f776161642f6d65646961)](https://packagist.org/packages/waad/media)[![License](https://camo.githubusercontent.com/4ad5465966c79e47f4b41692606ead3f67c878eb0efe6dbaeeea3cd0be00adaf/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f776161642f6d65646961)](https://packagist.org/packages/waad/media)

Media Files Package
===================

[](#media-files-package)

A Laravel package for managing media files across multiple storage drivers (local, S3, and any Laravel filesystem disk) with Eloquent model relationships. An alternative to [spatie/laravel-medialibrary](https://github.com/spatie/laravel-medialibrary).

Authors
-------

[](#authors)

- [Waad Mawlood](https://www.github.com/waadmawlood)
- [waad\_mawlood@outlook.com](mailto:waad_mawlood@outlook.com)

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

[](#requirements)

- PHP &gt;= 8.1
- Laravel 9, 10, 11, 12, or 13

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

[](#installation)

1. Install via Composer:

```
composer require waad/media
```

1. Publish the configuration and migration files:

```
php artisan vendor:publish --provider="Waad\Media\MediaServiceProvider"
```

1. Edit the config file at `config/media.php`
2. Run the migrations:

```
php artisan migrate
```

1. (Optional) Create storage symbolic links:

```
php artisan media:link
```

Usage
-----

[](#usage)

### Setup the Trait

[](#setup-the-trait)

Add the `HasMedia` trait to any Eloquent model:

```
use Waad\Media\HasMedia;

class Post extends Model
{
    use HasMedia;
}
```

### Uploading Media

[](#uploading-media)

```
// Upload a single file
$media = $post->addMedia($request->file('image'))->upload();

// Upload multiple files
$mediaItems = $post->addMedia($request->file('images'))->upload();

// Upload to a specific collection
$media = $post->addMedia($request->file('image'))
    ->collection('avatars')
    ->upload();

// Upload with custom options
$media = $post->addMedia($request->file('image'))
    ->collection('gallery')
    ->disk('s3')
    ->bucket('photos')
    ->label('Profile Picture')
    ->index(3)
    ->upload();
```

The upload service works with **any** Laravel filesystem disk — local, S3, or any custom driver. Just set the `disk()` and it handles the rest.

### Media Collections

[](#media-collections)

Register custom collections in your model to define specific media groups with their own storage configuration:

```
use Waad\Media\HasMedia;

class User extends Model
{
    use HasMedia;

    public function registerCollections(array $attributes = []): array
    {
        return [
            'avatar' => [
                'disk' => 's3',
                'bucket' => 'avatars', // directory in the bucket if whant save direct on bucket set empty string ''
                'label' => 'User Avatar',
                'single' => true,   // Only keeps one file
                's3' => [
                    'ttl_temporary_url' => config('media.s3.default_ttl_temporary_url', 5),
                ],
            ],
            'gallery' => [
                'disk' => 'public',
                'bucket' => 'photos',
                'label' => 'Photo Gallery',
                'single' => false,  // Allows multiple files
            ],
        ];
    }
}
```

When uploading to a registered collection, its disk, bucket, and label are applied automatically:

```
$user->addMedia($file)->collection('avatars')->upload();
```

### Retrieving Media

[](#retrieving-media)

```
// Get all media
$allMedia = $post->getMedia();

// Get media from a specific collection
$avatars = $post->getCollection('avatars');

// Get collection urls
$urls = $post->getCollectionUrls('avatars');
$urls = $post->getCollectionGroupUrls(); // get urls for multiple collections
$urls = $post->getCollectionGroupUrls(only: ['avatar', 'gallery']); // get urls for multiple collections
$urls = $post->getCollectionGroupUrls(except: ['avatar', 'gallery']); // get urls for multiple collections

// Get all collections by group
$collections = $post->getCollectionGroups();
$collections = $post->getCollectionGroups(only: ['avatar', 'gallery']); // only return the collections in the array
$collections = $post->getCollectionGroups(except: ['avatar', 'gallery']); // return all collections except the ones in the array

// Get collection as array
$array = $post->getCollectionArray('avatars');

// Get first or last media
$first = $post->getFirstMedia();
$last  = $post->getLastMedia();

// Get first or last media by collection
$first = $post->getFirstMediaByCollection('gallery');
$last  = $post->getLastMediaByCollection('gallery');

// Check if model has media in a collection
$post->hasMedia('avatars'); // true / false

// Find by ID (supports withTrashed)
$media = $post->mediaById($id);
$media = $post->mediaById($id, withTrashed: true);

// Filter by MIME type
$images = $post->mediaByMimeType('image/jpeg');

// Filter by MIME type and collection
$images = $post->mediaByMimeTypeByCollection('image/jpeg', 'gallery');
$images = $post->mediaByMimeTypeByCollection('image/jpeg', 'gallery', withTrashed: true);

// Filter by approval status
$approved    = $post->mediaApproved();
$disapproved = $post->mediaApproved(false);

// Statistics
$totalSize  = $post->mediaTotalSize();
$totalCount = $post->mediaTotalCount();
$totalCount = $post->mediaTotalCount(withTrashed: true);

// Statistics by collection
$size  = $post->mediaTotalSizeByCollection('gallery');
$size  = $post->mediaTotalSizeByCollection('gallery', withTrashed: true);
$count = $post->mediaTotalCountByCollection('gallery');
$count = $post->mediaTotalCountByCollection('gallery', withTrashed: true);
```

### Syncing Media

[](#syncing-media)

Use `syncMedia()` when you want to remove existing rows in a collection and optionally upload replacements. Deletes run **before** the new upload, and only affect the **active collection** (the default collection, or the one set with `collection()` on the chain, or the `$collection` argument passed to `upload()`). Media in other collections on the same model is left alone.

**Remove specific records, then upload**

Pass database IDs of `Media` rows to soft-delete before new files are stored. IDs that do not belong to the active collection are ignored (no rows removed).

```
$post->syncMedia($request->file('images'), [$oldMediaId1, $oldMediaId2])->upload();

$post->syncMedia($request->file('image'), [$oldMediaId])->collection('avatars')->upload();

// Illuminate\Support\Collection of IDs is also accepted
$post->syncMedia($file, collect([$id1, $id2]))->upload();
```

**Replace everything in the collection**

If you omit the second argument (or pass an empty array), existing media in that collection is removed first, then new files are uploaded. This is useful for a full “replace gallery” flow.

```
$post->syncMedia($request->file('images'))->upload();

$post->syncMedia($request->file('image'))->collection('gallery')->upload();
```

**Additive uploads (no removal)**

To upload new files without running the sync removal step, use `syncMediaWithoutDettached()` or chain `setIsWithDettachedSync(false)` after `syncMedia()`:

```
$post->syncMediaWithoutDettached($request->file('extra'))->upload();

$post->syncMedia($request->file('extra'))->setIsWithDettachedSync(false)->upload();
```

You can still combine with `collection()`, `disk()`, and the rest of the fluent API as with `addMedia()`.

### Deleting Media

[](#deleting-media)

```
// Delete specific media by ID (soft delete)
$post->deleteMedia($mediaId)->delete();

// Delete specific media by model
$post->deleteMedia($mediaModel)->delete();

// Delete multiple media by IDs
$post->deleteMedia([$id1, $id2, $id3])->delete();

// Delete all media for the model
$post->deleteMedia()->delete();
```

### Approving Media

[](#approving-media)

```
$media->approve();     // Mark as approved
$media->disApprove();  // Mark as disapproved

// Query approved media globally
$approved = \Waad\Media\Media::approved()->get();
```

### File Utilities

[](#file-utilities)

The upload service provides disk-aware utility methods that work with any storage driver:

```
$service = $post->addMedia(null);

// Check if a file exists
$service->fileExists('upload/photo.jpg');

// Get file size in bytes
$service->fileSize('upload/photo.jpg');

// Get file metadata (size, mimetype, last_modified)
$service->fileMetadata('upload/photo.jpg');

// Delete a file from disk
$service->deleteFile('upload/photo.jpg');

// Generate a temporary URL (S3 and compatible disks)
$service->disk('s3')->temporaryUrl('photos/secret.jpg', minutes: 10);
```

Filament
--------

[](#filament)

To use **waad/media** inside [Filament](https://filamentphp.com) admin panels, install the companion package [**waad/filament-media**](https://github.com/waadmawlood/filament-media). It provides a `MediaUpload` form component that lines up with `HasMedia` and `registerCollections()` (single vs multiple files, disks, reordering via `index`, and sync behavior).

- **Plugin page (docs &amp; requirements):** [Media Library — Filament](https://filamentphp.com/plugins/waad-mawlood-media-library)
- **This package (filament plugin):** [waad/filament-media](https://github.com/waadmawlood/filament-media)

```
composer require waad/filament-media
```

See the Filament plugin documentation for setup, `MediaUpload::make(...)`, and table previews with `getCollectionUrls()`.

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

[](#configuration)

Customize the package in `config/media.php`:

```
return [
    // Media model class
    'model' => \Waad\Media\Media::class,

    // Database table name
    'table_name' => 'media',

    // Default storage settings
    'disk' => env('MEDIA_DISK', 'public'),
    'bucket' => env('MEDIA_BUCKET', 'upload'),
    'default_collection' => env('MEDIA_DEFAULT_COLLECTION', 'default'),

    // S3 configuration
    's3' => [
        'default_ttl_temporary_url' => env('MEDIA_DEFAULT_S3_TTL_TEMPORARY_URL', 5),
    ],

    // Map disk names to public URL prefixes (used by media:link and full_url)
    'shortcut' => [
        // 'public' => 'storage',
    ],

    // Append full_url attribute to the Media model
    'enable_full_url' => env('MEDIA_ENABLE_FULL_URL', true),

    // Auto-prune soft-deleted media after N days (via media:prune)
    'prune_media_after_day' => env('MEDIA_PRUNE_MEDIA_AFTER_DAY', 30),

    // Default approval status for newly uploaded media
    'default_approved' => env('MEDIA_DEFAULT_APPROVED', true),

    // Date format for created_at / updated_at serialization (null = Y-m-d H:i:s)
    'format_date' => env('MEDIA_DATE_FORMAT', null),
];
```

Media Model Attributes
----------------------

[](#media-model-attributes)

AttributeTypeDescription`basename`stringHashed file name on disk`filename`stringOriginal uploaded file name`path`stringFull path in storage`index`intOrder index (default 1)`label`stringCustom label`collection`stringCollection name`disk`stringStorage disk (hidden)`bucket`stringStorage bucket/folder (hidden)`mimetype`stringFile MIME type`filesize`intFile size in bytes`approved`boolApproval status`metadata`jsonAdditional metadata (e.g. image dimensions)`full_url`stringAppended: public URL to the fileArtisan Commands
----------------

[](#artisan-commands)

CommandDescription`php artisan media:link`Create symbolic links from disk roots to public paths based on the `shortcut` config. Use `--force` to recreate existing links.`php artisan media:prune`Permanently delete soft-deleted media records (and their files) older than `prune_media_after_day`.Features
--------

[](#features)

- Multiple file upload support
- Works with any Laravel filesystem disk (local, S3, etc.)
- Collection management with per-collection disk/bucket configuration
- Soft deletes with `withTrashed` support
- File approval system
- Automatic image metadata extraction (width, height)
- File utilities (exists, size, metadata, delete, temporary URL)
- Automatic file cleanup via `media:prune`
- Polymorphic media relationships
- File statistics (total size, total count) — global and per-collection
- Customizable date serialization format
- `syncMedia()` with collection-scoped deletes (by ID or full collection replace), plus additive uploads via `syncMediaWithoutDettached()` or `setIsWithDettachedSync(false)`

Testing
-------

[](#testing)

```
composer install
composer test
```

License
-------

[](#license)

[MIT](LICENSE.md) © Waad Mawlood

###  Health Score

49

↑

FairBetter than 94% of packages

Maintenance89

Actively maintained with recent releases

Popularity21

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity62

Established project with proven stability

 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 ~116 days

Recently: every ~187 days

Total

12

Last Release

8d ago

Major Versions

v1.8.3 → v2.1.42023-08-09

v1.1.0-beta.1 → v3.0.12023-10-22

v1.9.0 → v3.0.22023-12-24

3.x-dev → v4.0.02026-03-19

PHP version history (5 changes)v1.8.3PHP ^7.4|^8.0

v2.1.4PHP ^8.0

v1.1.0-beta.1PHP ^7.2.5|^8.0

1.x-devPHP ^7.2

v4.0.0PHP ^8.1

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/44348636?v=4)[Waad Mawlood](/maintainers/waadmawlood)[@waadmawlood](https://github.com/waadmawlood)

---

Top Contributors

[![waadmawlood](https://avatars.githubusercontent.com/u/44348636?v=4)](https://github.com/waadmawlood "waadmawlood (42 commits)")

---

Tags

alternativefile-uploadfileslaravellaravel-packagemediaspatie-media-libraryfilesmediaupload filelaravel-mediaWaad Mawloodmedia-php

###  Code Quality

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/waad-media/health.svg)

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

###  Alternatives

[talvbansal/media-manager

A media browser and uploader for laravel apps written in vue js and bootstrap

20927.9k4](/packages/talvbansal-media-manager)[asgardcms/media-module

Media module for AsgardCMS. Handles the media library.

1130.6k2](/packages/asgardcms-media-module)

PHPackages © 2026

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