Laravel File Uploads: Save Filename in DB with Folder and URL?

When uploading files with Laravel, how to store the filename in the DB? Should you store filename.png? Or, include the folder of avatars/filename.png? Or, the full path https://website.com/avatars/filename.png? Let me tell you my opinion.

This tutorial will have an avatar field on the register page and an avatar column in the users table. Let's see how we can save it.


The easiest way is to store just the filename and create a separate storage disk.

config/filesystems.php:

return [
 
// ...
 
'disks' => [
 
// ...
 
'avatars' => [
'driver' => 'local',
'root' => storage_path('app/public/avatars'),
'url' => env('APP_URL').'/storage/avatars',
'visibility' => 'public',
'throw' => false,
],
 
],
 
// ...
 
];

Then, when storing the file we need to specify the new avatars disk.

app/Http/Controllers/Auth/RegisteredUserController.php:

class RegisteredUserController extends Controller
{
// ...
 
public function store(Request $request): RedirectResponse
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:'.User::class],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
'avatar' => ['nullable', 'image'],
]);
 
if ($request->hasFile('avatar')) {
$avatar = $request->file('avatar')->store(options: 'avatars');
}
 
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'avatar' => $avatar ?? null,
]);
 
// ...
}
}

This way, your full URL filename https://website.com/storage/avatars/filename.png consists of three things:

  • Domain: https://website.com is stored in your APP_URL in .env file: so it is flexibly different for your local and production servers
  • Folder: /storage/avatars is in the config('disks.avatars.url') which corresponds to the internal structure of /storage/app/public/avatars described in the same config file. Both also can be flexibly changed if needed.
  • Filename: filename.png is the only thing actually landing in the DB column

To get the URL for the image in the Blade file, we would use URL method on the Storage facade providing the disk.

<img src="{{ Storage::disk('avatars')->url(Auth::user()->avatar) }}" alt="{{ Auth::user()->name }}" />

But what if, after some time, you would need to go from local disks to, let's say, Amazon S3?

The only change you would need to make is to change the disk, and maybe instead of the url, use the temporaryUrl method to provide the expiration time for the link.

<img src="{{ Storage::disk('s3')->temporaryUrl(Auth::user()->avatar, now()->addMinutes(5)) }}" alt="{{ Auth::user()->name }}" />
avatar

Ok in what format should we store the avatar "table->string, test, binary, integer, bigInteger, which of these do you thank would be the best for storing the avatar in the database ?

avatar

I personally store filename as string, and file itself in the filesystem on disk. I don't store the avatar itself as binary in the database.

avatar

I am able to save the image to the storage\app\public\avatars and after running the php artisan storage:link command it is in the public\storage\avatars file however it is not going to the database. How do you suggest we send it to the database. Please help with a code hint.

I have this in the database users table: $table->string('avatar')->nullable();

avatar

how are you saving it to the db?

avatar

RegisteredUserController


 public function store(Request $request): RedirectResponse
    {
        $request->validate([
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:'.User::class],
            'password' => ['required', 'confirmed', Rules\Password::defaults()],
            'timezone' => ['required', Rule::in(array_flip(timezone_identifiers_list()))],
            'avatar' => ['nullable', 'image'],
        ]);

        if ($request->hasFile('avatar')) {
            $avatar = $request->file('avatar')->store(options: 'avatars');
        }

        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
            'timezone' => timezone_identifiers_list()[$request->input('timezone', 'America/Chicago')],
            'avatar' => $avatar ?? null,
        ]);

avatar

please format your code using markdown, can't read it like that. And provide more code. How is $avatar set? Do you have avatar in the fillable?

avatar

how is this now? This is my first time using markdown.

avatar

did you add fillable to the user model for the avatar? it's just you made somewhere a simple mistake

avatar

 protected $fillable = [
        'name',
        'email',
        'password',
        'is_admin',
        'is_staff',
        'timezone',
        'avatar',
    ];
avatar

Would using the Uuid in the user make any difference I do not thank so but thought I might put that out here since I am using it as well

avatar

In the register.blade.php file I have


<form method="POST" action="{{ route('register') }}" enctype="multipart/form-data">

 <!-- Avatar Select -->
        <div class="mt-4 form-group row">
            <label for="avatar" class="col-md-4 col-form-label text-md-right">{{ __('Avatar (optional)') }}</label>

            <div class="col-md-6">
                <input id="avatar" type="file" class="form-control" name="avatar">
            </div>
        </div>


avatar

I got it to work thanks the photo is now comming up on the dashboard page and it is in the database as well

Thanks

avatar

How can I put an image change or update In the profile\partials\update-profile-information-form.blade.php file any workable code would be helpfull. I tryed to use the code from jetstream but it did not work.

avatar

Great Short Tutorial. Thank you @Povilas

I have a few questions about this

Where do you see the advantage when I create a disk and

$avatar = $request->file('avatar')->store(options: 'avatars');

instead of (without Disk)

$avatar = $request->file('avatar')->store('avatars', 'public');

And is there a reason to work with Storage instead of asset in the blade?

With Storage I get https://www.laravel...., and with asset() I get https://backend.laravel (I have multiple Subdomains in my project)

What do you think about Scoped Filesystem so that I use this instead of a new disk "avatars"? https://laravel.com/docs/10.x/filesystem#scoped-and-read-only-filesystems

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 59 courses (1057 lessons, total 42 h 44 min)
  • 79 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials