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

kmnurunnabi's avatar

Inertia Form with Upload

I was working in a social platform where I was to update my post with body and picture fields. But problem arise when I choose picture and then if I, submit, I got stuck with validation error showing The body field is required even if the value exists. Point is inertia useForm is picking all the values. But why I cannot tackle the validation error?

here is my PostController class

	<?php

namespace App\Http\Controllers;

use App\Http\Requests\UpdatePostRequest;
use App\Http\Resources\PostResource;
use App\Models\Post;
use App\Services\PostService;
use Illuminate\Http\RedirectResponse;
use App\Http\Requests\StorePostRequest;

class PostController extends Controller
{
    public function __construct(
        protected PostService $postService,
    ) {
    }

    public function edit(Post $post)
    {
        $this->authorize('edit', $post);

        // return view('post.edit', compact('post'));
        return inertia()->render('Posts/Edit', [
            'post' => $post,
        ]);
    }

    public function update(UpdatePostRequest $request, Post $post): RedirectResponse
    {
        dd($request->validated());
        $this->authorize('update', $post);

        $this->postService->update(
            $post,
            $request->safe()->except('picture'),
            $request->hasFile('picture') ? $request->file('picture') : null,
        );

        return to_route(
            'posts.show',
            ['post' => $post->id],
        )->with('success', 'Post has been updated successfully');
    }
}

here is PostService class

	<?php

namespace App\Services;

use App\Constants\MediaCollectionName;
use App\Models\Post;
use Illuminate\Support\Facades\DB;

class PostService
{
    public function update(Post $post, array $data, $image = null)
    {
        return DB::transaction(function () use ($post, $data, $image) {
            $post->update($data);

            if ($image) {
                $post
                    ->addMedia($image)
                    ->toMediaCollection(MediaCollectionName::POST_IMAGE);
            }

            return $post;
        }, 3);
    }
}

here is UpdatePostRequest class

	<?php

namespace App\Http\Requests;

use Illuminate\Validation\Rules\File;
use Illuminate\Foundation\Http\FormRequest;

class UpdatePostRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'body' => 'required',
            'picture' => ['nullable', File::image()->max('8mb')],
        ];
    }
}

here is Edit.vue file

	<template>
    <form @submit.prevent="submit" method="POST" enctype="multipart/form-data"
        :class="`bg-white border-2 border-black rounded-lg shadow mx-auto max-w-none px-4 py-5 sm:px-6 space-y-3${form.errors.body ? ' border-red-500' : ''}`">
        <div>
            <div class="flex items-start /space-x-3/">
                <!-- User Avatar -->
                <div class="flex-shrink-0">
                    <img class="h-10 w-10 rounded-full object-cover" :src="auth_user_profile" alt="AVATAR">
                </div>
                <!-- /User Avatar -->

                <!-- Content -->
                <div class="text-gray-700 font-normal w-full">
                    <textarea v-model="form.body"
                        class="block w-full h-32 p-2 pt-2 text-gray-900 rounded-lg border-none outline-none focus:ring-0 focus:ring-offset-0"
                        name="body" rows="2" placeholder="Need some changes?"></textarea>
                </div>
            </div>
        </div>

        <div>
            <!-- Card Bottom Action Buttons -->
            <div class="flex items-center justify-between">
                <div class="flex gap-4 text-gray-600">
                    <!-- Upload Picture Button -->
                    <div>
                        <input type="file" name="picture" id="picture" class="hidden"
                            @input="form.picture = $event.target.files[0]">

                        <label for="picture"
                            class="-m-2 flex gap-2 text-xs items-center rounded-full p-2 text-gray-600 hover:text-gray-800 cursor-pointer">
                            <span class="sr-only">Picture</span>
                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
                                stroke="currentColor" class="w-6 h-6">
                                <path stroke-linecap="round" stroke-linejoin="round"
                                    d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z">
                                </path>
                            </svg>
                        </label>
                    </div>
                    <!-- /Upload Picture Button -->

                </div>

                <div>
                    <!-- Post Button -->
                    <button :disabled="form.processing" type="submit"
                        class="-m-2 flex gap-2 text-xs items-center rounded-full px-4 py-2 font-semibold bg-gray-800 hover:bg-black text-white disabled:bg-gray-300">
                        <Loader v-if="form.processing" />
                        Post
                    </button>
                    <!-- /Post Button -->
                </div>
            </div>
            <!-- /Card Bottom Action Buttons -->
        </div>
    </form>

    <template v-if="form.errors">
        <ul v-for="(error, key) in form.errors" class="!mt-0">
            <li class="text-sm text-red-600 font-semibold">{{ error }}</li>
        </ul>
    </template>
</template>

<script>
import AppLayout from '@/Layouts/AppLayout.vue'

export default {
    layout: AppLayout
}
</script>

<script setup>
import Loader from '@/Components/Loader.vue'
import { usePage, useForm } from '@inertiajs/vue3'

const props = defineProps({
    post: Object,
})

const { auth_user_profile } = usePage().props

const form = useForm({
    body: props.post.body,
    picture: null,
})

const submit = () => {
    form.put(route('posts.update', props.post.id), {
        onSuccess: () => form.reset(),
    })
}
</script>

Please help me fix this issues

1 like
5 replies
ivangretsky's avatar
Level 1

Not sure if that will help with the issue, but noticed that the submit function uses form.put, while the method on the form itself is set to POST. Here it is stated that put should not be used for file uploads.

2 likes
ivangretsky's avatar

@kmnurunnabi Is it solved? if so, please mark it as such. And maybe share how you changed the code to make it work.

1 like
kmnurunnabi's avatar

@ivangretsky

<script setup>
import Loader from '@/Components/Loader.vue'
import { usePage, useForm, router } from '@inertiajs/vue3'

const props = defineProps({
    post: Object,
})

const { auth_user_profile } = usePage().props

const form = useForm({
    body: props.post.body,
    picture: null,
})

const submit = () => {
    router.post(route('posts.update', props.post.id), {
        ...form.data(),
        _method: 'put',
    }, {
        onBefore: () => form.processing = true,
        onSuccess: () => form.reset(),
        onFinish: () => form.processing = false,
    })
}
</script>

This is how the code works

2 likes

Please or to participate in this conversation.