laravelswaggermultipartform-datadocumentationput

Laravel API: PUT request with multipart/form-data returns empty request when using Swagger or Postman


I'm working on a Laravel 10.x project with an API that allows updating an event using a PUT /events/{id} endpoint. This endpoint accepts multipart/form-data to optionally upload an image and pdf, and also includes standard fields like title, description, event_date, location, etc.

Here’s the relevant Swagger/OpenAPI documentation for the request body:

#[OA\RequestBody(
    request: "EventUpdateRequest",
    required: true,
    content: [
        new OA\MediaType(
            mediaType: "multipart/form-data",
            schema: new OA\Schema(
                type: "object",
                properties: [
                    new OA\Property(property: "_method", type: "string", default: "PUT"),
                    new OA\Property(property: "title", type: "string", example: "Updated Event"),
                    new OA\Property(property: "sort_order", type: "integer", example: 1),
                    new OA\Property(property: "image", type: "string", format: "binary"),
                    new OA\Property(property: "pdf", type: "string", format: "binary")
                    // ... other fields
                ]
            )
        )
    ]
)]

The controller method:

/**
     * @OA\Post(
     *     path="/events/{id}",
     *     tags={"Events"},
     *     summary="Etkinlik güncelle",
     *     description="Belirtilen ID'ye sahip etkinliği günceller. Eğer dosya (image, pdf) içeren multipart/form-data kullanılıyorsa, `_method=PUT` alanı eklenmelidir.",
     *     security={{"sanctum": {}}},
     *     @OA\Parameter(name="id", in="path", required=true, description="Etkinlik ID'si", @OA\Schema(type="integer")),
     *     @OA\RequestBody(ref="#/components/requestBodies/EventUpdateRequest"),
     *     @OA\Response(response=200, ref="#/components/responses/StandardSuccess"),
     *     @OA\Response(response=422, ref="#/components/responses/ValidationError"),
     *     @OA\Response(response=404, ref="#/components/responses/NotFound"),
     *     @OA\Response(response=401, ref="#/components/responses/Unauthorized"),
     *     @OA\Response(response=403, ref="#/components/responses/Forbidden"),
     *     @OA\Response(response=500, ref="#/components/responses/ServerError")
     * )
     */
    public function update(UpdateEventRequest $request, int $id)
    {
        try {
            $dto = UpdateEventDTO::fromRequest($request);
            $image = $request->hasFile('image') ? $request->file('image') : null;
            $pdf = $request->hasFile('pdf') ? $request->file('pdf') : null;

            $event = $this->eventService->updateEvent($id, $dto, $image, $pdf);

            return ApiResponse::success(
                new EventResource($event),
                'Etkinlik başarıyla güncellendi'
            );
        } catch (InvalidArgumentException $e) {
            return ApiResponse::error('Etkinlik bulunamadı', 404);
        } catch (Exception $e) {
            return ApiResponse::error('Etkinlik güncellenirken bir hata oluştu', 500);
        }
    }

The Route:

Route::middleware('role:super_admin,editor')->group(function () {
        Route::get('/events/filters/upcoming', [EventController::class, 'upcoming']);
        Route::get('/events/filters/past', [EventController::class, 'past']);
        Route::get('/events/search', [EventController::class, 'search']);
        Route::get('/events/statistics', [EventController::class, 'statistics']);
        Route::get('/events/grouped-by-month', [EventController::class, 'groupedByMonth']);
        Route::put('/events/{id}/sort-order', [EventController::class, 'updateSortOrder']);
        Route::apiResource('events', EventController::class);
    });

In my Laravel 10 API project, I have a PUT /events/{id} endpoint that should accept multipart/form-data to upload files (e.g. image, pdf) along with regular fields like title and sort_order. I’m using Swagger UI to test this.

When I send a real PUT request with multipart/form-data, Laravel receives an empty request ($request->all() is empty). However, if I send the same request as POST and include _method=PUT in the form data, it works as expected.

The Swagger request body is correctly defined with multipart/form-data and file inputs. I would like to use the actual PUT method, not method spoofing. Is this a limitation in Laravel regarding multipart/form-data with PUT requests, or is there a reliable way to make this work as intended?


Solution

  • Use method spoofing. This is the most reliable and Laravel-supported way to handle file uploads when updating resources.

    Instead of sending a real PUT request, keep your HTTP method as POST and include a form-data field named _method with the value PUT. This allows PHP to correctly parse the multipart/form-data request, and Laravel will internally treat it as a PUT request.

    In Swagger or OpenAPI documentation, define the request method as POST and include _method in the form data with a default value of PUT. This ensures compatibility with Laravel and allows both file uploads and form fields to be handled properly.