Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

bufferoverflow's avatar

Spatie MediaLibrary default storage depending on the model

Hello!

So this package default storage path is: /public/{id}/image.jpg

But what happens if i use two models (posts and products) and i want to store them like this:

// Posts
/public/posts/{id}/image.jpg

// Products
/public/products/{id}/image.jpg

First i thought that i could use two disks pointing at those folders, but i think that creating disk for each model is not the correct solution.

Later i found the "custom folder struture" at the package docs https://docs.spatie.be/laravel-medialibrary/v7/advanced-usage/using-a-custom-directory-structure, but i thought it was too complex for only adding a folder in the file path.

Do you know a simpler way to divide media in model folders?

0 likes
11 replies
bobbybouwmann's avatar
Level 88

The "custom folder struture" part is the only way to actually rename the folders!

You can really easily create a new class that extends that interface and return some path for your models

class CustomPathGenerator implements PathGenerator
{
    public function getPath(Media $media) : string
    {
        if ($media instanceof Post) {
            return 'posts/' . $media->id;
        }

        if ($media instanceof Product) {
            return 'products/' . $media->id;
        }
    }

    public function getPathForConversions(Media $media) : string
    {
        return $this->getPath($media) . 'conversions/';
    }

    public function getPathForResponsiveImages(Media $media): string
    {
        return $this->getPath($media) . 'responsive/';
    }
}

Don't forget to update the config and point to the class

'path_generator' => CustomPathGenerator::class,

Source: https://github.com/spatie/laravel-medialibrary/blob/7.0.0/tests/Unit/PathGenerator/CustomPathGenerator.php

9 likes
gwa's avatar

@bobbybouwmann I added this to the getPath to automatically use a Model-based folder Structure

 public function getPath(Media $media) : string{
        return substr(strrchr($media->model_type, "\"), 1).'/'.$media->id.'/';
}
2 likes
gcwilliams's avatar

The easiest way really is to just create multiple disks.

 'disks' => [
        'postFiles' => [
            'driver' => 'local',
            'root'   => storage_path('app/posts'),
        ],

        'productFiles' => [
            'driver' => 'local',
            'root'   => storage_path('app/products'),
        ],

Then within your model do this:

 public function registerMediaCollections()
    {
        $this->addMediaCollection('posts')
            ->useDisk('postFiles');
    }

That to me at least is cleaner then using a custom path generator and allows you to store things externally if you wish.

9 likes
madsynn's avatar

Giving props to @gcwilliams i would like to add to his answer.

If you add these to the filesystem config under disks it will solve the url part of your question also. Here is an updated answer.

'disks' => [
        'postFiles' => [
            'driver' => 'local',
            'root'   => storage_path('app/posts'),
            'url'        => env('APP_URL') . '/posts',
            'visibility' => 'public'
        ],

        'productFiles' => [
            'driver' => 'local',
            'root'   => storage_path('app/products'),
            'url'        => env('APP_URL') . '/products',
            'visibility' => 'public'
        ],

Paths files are stored at are:

public/posts/{id}/

public/products/{id}/

This will make the visible url links look like this.

www.yoursite.com/products/{product}

www.yoursite.com/posts/{post}

Anyway hope it helps someone.

6 likes
Neeraj1005's avatar

@madsynn @gcwilliams you forgot about storage link symlink

public_path('posts') => storage_path('app/posts'),

And run php artisan storage:link

1 like
Toskan's avatar

In v7 this works

    protected function getBasePath(Media $media): string
    {

        $rv = $media->getKey();

        if ($media->model_type == Matrixcolor::class) {
            $collection = $media->collection_name;
            $rv = 'matrixcolors/'. $collection . '/' . $media->getKey();
        }
        return $rv;
    }


1 like
trifid's avatar

I'm trying to apply @bobbybouwmann solution, with Laravel Media Library 8.0.0 but I'm missing something, as I get a "PathGenerator Class doesn't exist".

Where should I place the class?

What dependencies should it use?

Is it enough to declare it as "path_generator" variable in media-library.php config file?

Thank you!

chetan1517's avatar

@bobbybouwmann posting this answer for others so that it will be helpful.

It looks like this is wrong.

public function getPath(Media $media) : string
{
    if ($media instanceof Post) {
        return 'posts/' . $media->id;
    }

    if ($media instanceof Product) {
        return 'products/' . $media->id;
    }
}

If you are injecting Media class object, how is it possible that it will be instance of Post ?

I believe following is the correct one

    if ($media->model_type == 'App\Models\Post') {
		return 'posts/' . $media->id.'/';
    }

I am using media library v11 with laravel 10 and php 8.1

2 likes
DewiH's avatar

@chetan1517 Thank you!! I was wondering how to make custom subfolders to save the uploaded files. It's working for me.

Please or to participate in this conversation.