PHPackages                             codewiser/simple-files - 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. codewiser/simple-files

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

codewiser/simple-files
======================

Light-weight file manager for Laravel

v1.1.2(2mo ago)02921MITPHPPHP ^8.1

Since Feb 18Pushed 2mo ago2 watchersCompare

[ Source](https://github.com/C0deWiser/simple-storage)[ Packagist](https://packagist.org/packages/codewiser/simple-files)[ RSS](/packages/codewiser-simple-files/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (6)Versions (25)Used By (1)

Simple lightweight storage
==========================

[](#simple-lightweight-storage)

No database, only filesystem.

Every model keeps its files isolated from each over. Path to files formed from the model's morph name and its primary key.

> Remember to `enforceMorphMap` in `AppServiceProvider`!

Define storage
--------------

[](#define-storage)

Implement `Attachmentable` contract on `Model`.

In the example below the model will keep files on default disk at `post/{id}`path.

```
use Codewiser\Storage\Attachmentable;
use Codewiser\Storage\Storage;
use Codewiser\Storage\StorageContract;
use Illuminate\Database\Eloquent\Model;

class Post extends Model implements Attachmentable
{
    public function storage(string|\BackedEnum $bucket = null): StorageContract
    {
        return Storage::make($this, bucket: $bucket);
    }
}
```

### Uploading files

[](#uploading-files)

You may add files from `UploadedFile`, from a local path or remote url. You may upload multiple files at once.

```
use Illuminate\Http\Request;

class Controller {
    public function attach(Request $request, Post $post) {

        return $post->storage()
            ->upload($request->allFiles())
            ->toArray();
    }
}
```

```
[{
    "path": "post/1/test.png",
    "url": "/storage/post/1/test.png",
    "name": "test.png",
    "size": 6434,
    "hash": "d41d8cd98f00b204e9800998ecf8427e",
    "mime_type": "image/png",
    "last_modified": "2025-02-18T12:29:46+00:00"
}]
```

### Removing files

[](#removing-files)

File `path` attribute is a relative path to a disk, e.g. `post/1/test.png`. Use `path` attribute to delete a file. You may delete multiple files at once.

```
use Illuminate\Http\Request;

class Controller {
    public function detach(Request $request, Post $post) {
        $post->storage()
            ->delete($request->input('unlink'));

        return response()->noContent();
    }
}
```

To remove all files call `flush` method on `Storage`:

```
$files = $post->storage()->flush();
```

### List files

[](#list-files)

To get collection with all files call `files` method on `Storage`:

```
$files = $post->storage()->files();

return $files->toArray();
```

`Storage` object is `Arrayable` too. It returns the same:

```
$post->storage()->toArray();
// Is equivalent to
$post->storage()->files()->toArray();
```

### File object

[](#file-object)

File object has the same methods as Laravel Storage Facade: `exists`, `size`, `lastModified`, `delete`, `checksum`, `url` etc.

Every stored file represented with such an array:

```
{
  "path": "post/1/test.png",
  "url": "/storage/post/1/test.png",
  "name": "test.png",
  "size": 6434,
  "hash": "d41d8cd98f00b204e9800998ecf8427e",
  "mime_type": "image/png",
  "last_modified": "2025-02-18T12:29:46+00:00"
}
```

`File` object implements `Responsable` and `Attachable`, so you may use it as `Response` and in `Notification` or `Mailable`.

Singular Storage
----------------

[](#singular-storage)

Sometimes we need the model to have only one file. We may create such a storage:

```
use Codewiser\Storage\Attachmentable;
use Codewiser\Storage\Storage;
use Codewiser\Storage\StorageContract;
use Codewiser\Storage\Singular;
use Illuminate\Database\Eloquent\Model;

class Post extends Model implements Attachmentable
{
    public function storage(string|\BackedEnum $bucket = null): StorageContract|Singular
    {
        return Storage::make($this, disk: 'public', bucket: $bucket)->singular();
    }
}
```

When you upload the next file to a storage, all previous files will be removed.

Singular storage has only one file, so `files` collection will contain only one element maximum. You may use `file` method instead.

```
$post->storage()->toArray();
// Is equivalent to
$post->storage()->file()->toArray();
```

Storage Pool
------------

[](#storage-pool)

The model may have few storages at the same time. Storages must have unique names (aka buckets).

```
use Codewiser\Storage\Attachmentable;
use Codewiser\Storage\Storage;
use Codewiser\Storage\Singular;
use Codewiser\Storage\StorageContract;
use Illuminate\Database\Eloquent\Model;

class Post extends Model implements Attachmentable
{
    public function storage(string|\BackedEnum $bucket = null): StorageContract|Singular
    {
        return match ($bucket)

            // One cover
            'cover' => Storage::make($this, bucket: $bucket)
                ->singular(),

            // Many docs
            'docs'  => Storage::make($this, bucket: $bucket),

            default => throw new \InvalidArgumentException("Bucket is not supported"),
        };
    }
}
```

Then we may get the exact bucket:

```
$docs = $post->storage('docs')->files();
$cover = $post->storage('cover')->file();
```

### Default storage

[](#default-storage)

It is allowed to have one default storage in a pool:

```
use Codewiser\Storage\Attachmentable;
use Codewiser\Storage\Storage;
use Codewiser\Storage\Singular;
use Codewiser\Storage\StorageContract;
use Illuminate\Database\Eloquent\Model;

class Post extends Model implements Attachmentable
{
    public function storage(string|\BackedEnum $bucket = null): StorageContract|Singular
    {
        return match ($bucket)

            // Named bucket
            'docs'  => Storage::make($this, bucket: $bucket),

            // Default bucket
            null => Storage::make($this)->singular(),

            default => throw new \InvalidArgumentException("Bucket is not supported"),
        };
    }
}
```

Call `storage` without bucket name to get the default one.

```
$cover = $post->storage()->file();
$docs = $post->storage('docs')->files();
```

### Pool response

[](#pool-response)

You may add a method to a model, that will return `Pool` object with all buckets defined:

```
use Codewiser\Storage\Attachmentable;
use Codewiser\Storage\Pool;
use Codewiser\Storage\Storage;
use Codewiser\Storage\Singular;
use Codewiser\Storage\StorageContract;
use Illuminate\Database\Eloquent\Model;

class Post extends Model implements Attachmentable
{
    public function pool(): Pool
    {
        return Pool::make()
            ->addBucket(Storage::make($this)->singular())
            ->addBucket(Storage::make($this, bucket: 'docs'));
    }

    public function storage(string|\BackedEnum $bucket = null): StorageContract|Singular
    {
        return $this->pool()->getBucket($bucket);
    }
}
```

Then you may use this method in api resource:

```
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class PostResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            ...parent::toArray($request),

            'files' => $this->pool()->toArray()
        ];
    }
}
```

Pool `toArray` method will return an array with every bucket and its file(s). Singular storage provides `file` attribute, that may be `null`if no file were uploaded. Base storage provides `files` array, that may be empty.

```
[
    {
        "bucket": null,
        "file": {
          "path": "post/1/test.png",
          "url": "/storage/post/1/test.png",
          "name": "test.png",
          "size": 6434,
          "hash": "d41d8cd98f00b204e9800998ecf8427e",
          "mime_type": "image/png",
          "last_modified": "2025-02-18T12:29:46+00:00"
        }
    },
    {
        "bucket": "docs",
        "files": [
          {
            "path": "post/1/docs/test.png",
            "url": "/storage/post/1/docs/test.png",
            "name": "test.png",
            "size": 6434,
            "hash": "d41d8cd98f00b204e9800998ecf8427e",
            "mime_type": "image/png",
            "last_modified": "2025-02-18T12:29:46+00:00"
          }
        ]
    }
]
```

Downloading files
-----------------

[](#downloading-files)

The file is directly accessible only then published in public local filesystem. In other cases — private or cloud filesystem — application needs a controller to make files accessible to the users.

Let's say we have such a private disk in `config/filesystems.php`:

```
'local' => [
    'driver' => 'local',
    'root' => storage_path('app/private'),
    'url' => env('APP_URL').'/private',
    'serve' => true,
    'throw' => false,
    'report' => false,
],
```

If so, file url would be about `private/post/1/test.png` (for default bucket) or `private/post/1/bucket/test.png` (for named bucket).

We suggest to use a controller `\Codewiser\Storage\StorageController`, that is looks so:

```
use Codewiser\Storage\File;
use Codewiser\Storage\Storage;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

class StorageController
{
    public function __invoke(Request $request, string $model, string $id, string $bucket, string $filename = null): Responsable
    {
        if (is_null($filename)) {
            $filename = $bucket;
            $bucket = null;
        }

        $storage = Storage::resolve($model, $id, $bucket);

        Gate::authorize('view', $storage->owner());

        return $storage->files()->sole(
            fn(File $file) => $file->filename() == $filename
        );
    }
}
```

All you need is to declare a route:

```
use Codewiser\Storage\StorageController;
use Illuminate\Support\Facades\Route;

Route::get('private/{model}/{id}/{bucket}/{filename?}', StorageController::class);
```

###  Health Score

44

—

FairBetter than 92% of packages

Maintenance86

Actively maintained with recent releases

Popularity14

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

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

Recently: every ~23 days

Total

24

Last Release

74d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/ce23eaf7ae48d79d2b07efc83ff7cecb2664f428bd531b167b90169423f1453d?d=identicon)[Cellard](/maintainers/Cellard)

---

Top Contributors

[![Cellard](https://avatars.githubusercontent.com/u/1220316?v=4)](https://github.com/Cellard "Cellard (34 commits)")

---

Tags

laravelstorage

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/codewiser-simple-files/health.svg)

```
[![Health](https://phpackages.com/badges/codewiser-simple-files/health.svg)](https://phpackages.com/packages/codewiser-simple-files)
```

###  Alternatives

[league/flysystem-aws-s3-v3

AWS S3 filesystem adapter for Flysystem.

1.6k263.6M790](/packages/league-flysystem-aws-s3-v3)[rahulhaque/laravel-filepond

Use FilePond the Laravel way

261114.4k2](/packages/rahulhaque-laravel-filepond)

PHPackages © 2026

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