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

trevorpan's avatar

Storing files in a sub directory on the public disk causes FileNotFoundException

https://laravel.com/docs/7.x/filesystem#the-public-disk

The below stores quick, fine and reliable using Spatie MediaLibrary.

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

When I try this below it gives errors: League\Flysystem\FileNotFoundException: File not found at path: 161/5e9f84d32a983_Screen-Shot-2020-04-17-at-7.54.14-AM.png in /Users/trevorpan/code/bidbird/vendor/league/flysystem/src/Filesystem.php:389

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

There's a https://laracasts.com/series/laravel-6-from-scratch/episodes/64 Jeffrey says be sure to store files in /app/public/avatars roughly at 13:11.

From the docs. I find this confusing. Do images directory automatically get put in the public?

'links' => [
    public_path('storage') => storage_path('app/public'),
    public_path('images') => storage_path('app/images'),
],

If you understand what's happening I'd like to know ~

0 likes
11 replies
Snapey's avatar

One thing. Don't use env() in your code. Also, never store full URL in your database because it prevents you changing URL (from staging to live for instance).

You should store your images in storage/app/public (and any sub folder thereof)

Your images will then be visible at public/storage/ (and any sub folder thereof)

Provided you have created the symlink with php artisan storage:link

That new feature in 7.7 should have nothing to do with your problem.

1 like
trevorpan's avatar

I see @snapey , yea it just came in my email this morning ...

That's the thing that's strange

'root' => storage_path('app/public/media'),

It seems like that would create a sub directory and then the media folders e.g. 1 , 2, 3 are there.

Now the spatie docs: https://docs.spatie.be/laravel-medialibrary/v3/installation-setup/

  'disks' => [
        'media' => [
            'driver' => 'local',
            'root'   => public_path('media'),
        ],

I've tried clearing the sym link with this post, to try to refresh, not sure if it's necessary: https://laracasts.com/discuss/channels/laravel/how-i-undo-php-artisan-storagelink

Just gave the above a run (right from media library docs) and got the same not found. I notice laravel has public_storage('app/public') but Spatie uses public_path('media') it's just confusing.

Gave this a shot, which still gets the error...

       'media' => [
            'driver' => 'local',
            'visibility' => 'public',
            'root' => public_path('public/media'),
        ],

Thank you for checking into this...

trevorpan's avatar

@snapey, as you usual you point in the right direction!!!

This was unbelievably painful.

You can publish the vendor directory with this package, however, the out of the box vendor vendor/spatie/laravel-medialibrary/config/medialibrary.php file had public as the disk...(which is actually contrary to the docs page "standard" config link above where the docs designated media).

So, not sure why this vendor file still overrides the published app/config/MediaLibrary.php file.

But that's not all - - - the laravel filesystems.php file is hard to grasp until you 'get' it. Not sure if there's a rationality to this or if it kind just assumed this method.

As you can see the media disk assumes you're in public/storage directory - based on the symbolic links. Why this was so frustrating is you'd assume it'd have the same naming convention of storage/media.

storage/app/public/media assumes you're in public/storage.

    'disks' => [

	// not clear from laravel docs if links is supposed to be under 'disks' or above or below, or if it matters
        'links' => [
            public_path('storage/media/') => storage_path('app/public/media/'),
        ],

        'media' => [
            'driver' => 'local',
            'root' => public_path('media'),
            'visibility' => 'public',
        ],

Really hope that helps someone in the future! It's also quite possible this is not the best arrangement, too.

Snapey's avatar

Not sure I follow.

public disk is rooted at storage/app/public (when using default storage path)

and storage_path is defined at

public function storagePath()
{
    return $this->storagePath ?: $this->basePath.DIRECTORY_SEPARATOR.'storage';
}

and public path is defined at

public function publicPath()
{
    return $this->basePath.DIRECTORY_SEPARATOR.'public';
}

so public_path is confirmed as project/public and storage path is project/storage

so the root of the public disk is defined as project/storage/app/public from filesystems.php

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

The docs state that the disk_name is specified as

    /*
     * The disk on which to store added files and derived images by default. Choose
     * one or more of the disks you've configured in config/filesystems.php.
     */
    'disk_name' => env('MEDIA_DISK', 'public'),

which means that it is the public disk unless you override it with a MEDIA_DISK env variable.

This is so that you can test locally and have a different disk in production.

Spatie docs go on to say

By default, the media library will store its files on Laravel’s public disk. If you want a dedicated disk you should add a disk to config/filesystems.php.

So I expect media library to create numbered folders at project/storage/app/public

Since the default symlink creates a link between project/storage/app/public and project/public/storage/ then I can see these numbered folders in the url myhost.com/storage/

trevorpan's avatar

shoot ahh...

"laravel/framework": "^v6.0.0",

I got the impression for Jeffrey's video that if you have different types of things they should be placed in the public/(whatever your folder). That seemed to make sense as there may be project media files in one public/media directory, but you may also want public/avatars e.g.

This is where I referenced the media,

https://docs.spatie.be/laravel-medialibrary/v3/installation-setup/

return [
    ...
    'disks' => [
        'media' => [
            'driver' => 'local',
            'root'   => public_path('media'),
        ],
    ... 
];
Snapey's avatar

so where did your media disk come from?

and links is not necessary as this was only introduced in L7?

trevorpan's avatar

@snapey

You've definitely exposed a weakness. I was looking at Laravel v7, and Spatie v3 docs - both versions not in use here!

Thank you for that, have be more mindful.

so this issue https://github.com/spatie/laravel-medialibrary/issues/190 talks about the very thing I've run into. Without the 'url' => env('APP_URL').'/storage/media', the media is not stored/retrieved properly.

Prior to adding the env line the numbered folders simply did not appear. After adding it, remove symlinks, and then re-linking - the numbered folders were stored in both places.

Here's the latest setup:

// not sure how to implement this: 'root' => public_path('storage/media'),

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

However, you've already pointed out the env is not something to use here for staging. How do you get around this?

Snapey's avatar
Snapey
Best Answer
Level 122

I'll go back on what I said before. I said don't use env in your code - but of course using it in config files is fine because these can be cached.

So, there are two options;

  1. use env('APP_URL') and ensure it is set correct in each environment

  2. remove it and just rely on the path being simply relative to root. with 'url' => '/storage/media' then there is nothing to get wrong in the config. This is only an option if you correctly host in each location with public as the document root and therefore / is always the public folder.

Just one observation from your comment - "the numbered folders were stored in both places" remember that they are not. They are stored in one place (storage/app/public/media) but they are aliased to public/storage/media

1 like
trevorpan's avatar

@snapey

That second line seems the most bullet proof.

I read this comment on twitter today that said in software going from 1 to 2 is ridiculously hard but 2 - ♾ is pretty smooth.

Thank you again ~ almost there just 18 months for my first website!!

Please or to participate in this conversation.