Skip to main content

Black Friday 2025! Only until December 1st: coupon FRIDAY25 for 40% off Yearly/Lifetime membership!

Read more here

File Upload with Spatie Media Library

Premium
8 min read

Now, let's add a file upload field to the Tasks CRUD.


Prepare Back-End: Spatie Media Library

Let's install a well-known package spatie/laravel-medialibrary to handle the file data.

composer require "spatie/laravel-medialibrary"

Then, according to its documentation, we need to publish and run migrations:

php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
php artisan migrate

Finally, we add the appropriate Traits to the Task Model file.

app/Models/Task.php:

use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
 
// ...
 
class Task extends Model
class Task extends Model implements HasMedia//[ tl! ++]
{
use HasFactory;
use InteractsWithMedia;
 
// ...

Ok, the package installation/configuration is ready. Let's dive into the forms.


File Upload in Create Form: Front-End

This time, let's start with the front end.

For reference, here's the Inertia documentation on File Uploads.

Let's see how our Create.tsx file needs to change.

  • We add media to the CreateTaskForm and useForm
  • We add a progress indicator from Inertia
  • We add the <Input> for the file picker

We start with the TypeScript changes...

The Full Lesson is Only for Premium Members

Want to access all of our courses? (29 h 14 min)

You also get:

54 courses
Premium tutorials
Access to repositories
Private Discord
Get Premium for $129/year or $29/month

Already a member? Login here

Comments & Discussion

RA
Richard A. Hoyle ✓ Link copied!

Getting error from the resources/js/pages/tasks/create.tsx and the edit.tsx The files both work and I am getting the info on to the web page. When looking over the pages the only thang I can see that is being marked as ban are the <Input id="media" onChange={(e) => setData('media', e.target.files[0])} className="mt-1 block w-full" type="file" /> This is on both pages On the Edit.tsx you mentioned the put problem I am getting this as well.

PS. I am using PhpStorm as my editor.

PK
Povilas Korop ✓ Link copied!

Wait, so the files are working but you're worried about IDE warnings, only?

J
Joe ✓ Link copied!

You can get rid of the linter error by applying a conditional to make sure e.target.files[0] is not null first:

onChange={(e) => {
    const files = e.target.files;
    if (files && files.length > 0) {
        setData('media', files[0]);
    }
}}
RA
Richard A. Hoyle ✓ Link copied!

Not able to see File or Categories on the index.tsx page but can see both on the edit.tsx page and in the database; any suggestions?

H
Harti ✓ Link copied!

did you run php artisan storage:link ?

T
TuanTQ ✓ Link copied!

I get error Add fillable property [media] to allow mass assignment on [App\Models\Task]. and if i remove media in form request, it will work. is there anyone like me ?

K
krd313 ✓ Link copied!

in the index and edit pages I do not see any image, I see a box and a small icon, when I click on this I get taken to a fresh web page with "The requested URL was not found on this server." I think it maybe looking at the wrong directory how do I change that ?

PK
Povilas Korop ✓ Link copied!

It probably depends on how you configured your web-server and .env file.

Have you run "php artisan storage:link"?

What is the actual URL it is trying to load?

K
krd313 ✓ Link copied!

I have run the storage link, when I click on the icon it shows 'http://localhost/storage/3/plus.1024x1024.png", if I go into storage/app/public/3/ the file is there. Why does it create a directory for each file?

PK
Povilas Korop ✓ Link copied!

That's how spatie/laravel-medialibrary works, creating a directory with ID for each file. With why this file isn't shown, check two things

  • In .env file: APP_URL=http://localhost
  • And run php artisan storage:link
K
krd313 ✓ Link copied!

I have re-run "php artisan storage:link" and got " ERROR The [public/storage] link already exists. " . in the .env file the setting was "APP_URL=http://localhost". I went through a the exercise with a fresh install and got the same result. I am using vscode on linux mint. I have checked my files against the gitHub commit but have not been able to find the error. I think its saving to the correct place but I don't think its looking in the correct place.

PK
Povilas Korop ✓ Link copied!

Maybe something is different on Linux Mint specifically or your web-server, like some folder/file permissions are different? I don't see any other obvious errors, either, if the URL "http://localhost/storage/3/plus.1024x1024.png" doesn't work in your browser.

K
krd313 ✓ Link copied!

Thanks for the suggestions.

DS
Dmytro Sakharuk ✓ Link copied!

When sending a request via router.post, the errors object will be empty. Therefore, it is recommended to use post and transform

let { data, setData, errors, reset, post, transform, processing, progress } = useForm<Required<EditTaskForm>>({
		// ...
});

transform((data) => ({
	...data,
	_method: 'PUT'
}));

const editTask: FormEventHandler = (e) => {
	e.preventDefault();
	
	post(
		// ...
AF
Angel Fabian Silvera ✓ Link copied!

I made 2 changes to my code because they are using string in media and thats give me TypeScript error you can only omit it but i prefer to change the Type of Props

type EditTaskForm = {
    name: string
    is_completed: boolean
    due_date: string
    media?: File | null;
    categories: string[];
};

 const { data, setData, errors, put, reset, processing, progress } = useForm<Required<EditTaskForm>>({
        name: task.name,
        is_completed: task.is_completed,
        due_date: task.due_date,
        media: null,
        categories: task.task_categories.map((category) => category.id.toString()),
 });

And in the Input

<Input
 id="media"
 onChange={(e) => setData('media', e.target.files?.[0] ?? null)}
 className="mt-1 block w-full"
 type="file"
 />