Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Multipart Middleware Laravel Package

illuminatech/multipart-middleware

Laravel middleware that parses multipart/form-data requests for PUT, PATCH, and other non-POST methods, populating the request with regular input and uploaded files so you can handle file uploads on any HTTP verb.

View on GitHub
Deep Wiki
Context7

Getting Started

Pull the package in with Composer:

composer require illuminatech/multipart-middleware

The package exposes a single middleware (Illuminatech\MultipartMiddleware\MultipartFormDataParser) that fills the gap PHP leaves open: it parses multipart/form-data bodies for HTTP verbs other than POST (which Laravel/PHP handle natively). Without it, $request->input() and $request->file() return nothing for PUT, PATCH, or DELETE requests whose body is multipart/form-data.

Register it globally so every request goes through the same parsing path — this is where you want it, because SPA clients, fetch calls with FormData, and API tests will all get consistent behavior:

// app/Http/Kernel.php (Laravel ≤10)
protected $middleware = [
    // ...
    \Illuminatech\MultipartMiddleware\MultipartFormDataParser::class,
];
// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
    $middleware->append(\Illuminatech\MultipartMiddleware\MultipartFormDataParser::class);
})

First useful scenario: an endpoint that supports file uploads for both creation and update. POST /documents works out of the box; PUT/PATCH /documents/{id} with Content-Type: multipart/form-data would normally arrive with an empty $request->all(). With this middleware installed, the same controller code works for both verbs:

public function update(Request $request, Document $document)
{
    $data = $request->validate([
        'title' => 'required|string',
        'file'  => 'nullable|file|max:10240',
    ]);

    if ($request->hasFile('file')) {
        $document->attach($request->file('file'));
    }

    $document->update($data);
    return $document;
}

Look first at the middleware class itself and its test suite — the package is small, so a quick skim is faster than hunting for docs.

Implementation Patterns

Global vs. route-scoped registration. Global registration is the safest default because it makes every route behave the same way regardless of verb. Register per-route only if you have a strong reason (e.g., a legacy gateway that sends malformed bodies for unrelated endpoints and you want to isolate the parse cost). Even then, route middleware is fine as long as the routes are explicitly the multipart-accepting ones.

Combine with _method / method-override for browser forms. HTML <form> elements only support GET and POST. If you use @method('PUT') in Blade, the request arrives as POST with _method=PUT and PHP parses it for you — no middleware needed. This middleware matters when the client actually sends multipart with a non-POST verb, which is typical for fetch + FormData in SPAs:

const fd = new FormData();
fd.append('title', 'Q3 report');
fd.append('file', fileInput.files[0]);

await fetch(`/api/documents/${id}`, {
  method: 'PUT',                // actual verb, not POST + _method
  body: fd,                     // no Content-Type header — let the browser set the boundary
  headers: { 'Accept': 'application/json' },
});

Do not set Content-Type manually in that fetch call; the browser must set it with the boundary.

Compose with validation and policies. Because the middleware populates the Symfony ParameterBag and FileBag, standard Laravel tools — $request->validate(), FormRequests, UploadedFile rules, mimes:pdf, dimensions — work unchanged. There's no new API to learn; you're just restoring the behavior POST already has.

Pair it with a FormRequest for consistent controllers.

class UpdateDocumentRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'title' => ['sometimes', 'string', 'max:255'],
            'file'  => ['sometimes', 'file', 'mimes:pdf,docx', 'max:20480'],
            'tags'  => ['sometimes', 'array'],
            'tags.*'=> ['string'],
        ];
    }
}

Testing. UploadedFile::fake() plus $this->put('/documents/1', [...]) exercises the middleware because Laravel's test helpers set the request content type from the array/upload payload. Assert both the fields and the file:

$response = $this->put("/documents/{$doc->id}", [
    'title' => 'Revised',
    'file'  => UploadedFile::fake()->create('report.pdf', 500, 'application/pdf'),
]);

$response->assertOk();
Storage::disk('local')->assertExists('documents/report.pdf');

If a test fails with empty $request->all(), check whether you're using $this->put(..., [], [], $files) incorrectly — you want the merged array form above so the test builds a proper multipart body.

Integration with storage and queues. For large uploads, prefer $request->file('file')->store(...) (streams to disk) before dispatching a job, rather than putting the UploadedFile on a queue. The middleware doesn't change that best practice: the UploadedFile is a pointer to a temp file that expires when the request ends.

Pair with method-spoofing when proxies are involved. Some load balancers rewrite verbs. If your edge terminates PUT as POST and uses X-HTTP-Method-Override, the middleware isn't the piece that unspoofs it — Laravel's MethodNotAllowed flow is handled elsewhere. Keep an eye on this if you see files arriving populated but the route never matching a PUT handler.

Gotchas and Tips

Licensing needs verification before adoption. The repo is tagged NOASSERTION, which means the license could not be automatically determined. Open LICENSE/composer.json in the repo and get sign-off from whoever owns OSS compliance. Do not blindly add it.

post_max_size and upload_max_filesize still bite. The middleware parses the body, but PHP has already enforced these ini limits by the time it runs. If uploads of 20 MB silently arrive empty, bump both ini values and confirm file_uploads = On. Symptom to watch for: $_FILES is empty AND Content-Length is greater than zero — that's a size limit, not a middleware bug.

Streams are read once. The middleware reads the raw body to parse it. If you later call $request->getContent() on a multipart request, expect an empty or already-consumed body — you should read the parsed fields and files instead. Don't try to re-parse multipart manually after the middleware has run.

Beware double parsing on POST. PHP handles POST multipart natively. The middleware is designed to skip/handle that, but if you subclass or wrap it, verify you aren't parsing POST bodies twice — that can duplicate file entries in $request->files.

Sparse documentation. With ~33 stars and no clear license assertion, expect the README to be thin. The middleware class and its PHPUnit tests are the most reliable spec for exact behavior (which content types it recognizes, what it does on parse errors, whether it throws or logs). Read those before you build custom handling on top.

Error behavior on malformed bodies. If a client sends truncated multipart (interrupted upload, bad boundary), decide whether the middleware should throw or fall through. In most apps you want a clean 422, so wrap validation in a FormRequest that also asserts $request->isMethod('put') && $request->header('Content-Type') looks right, and add a short-circuit check early:

if ($request->header('Content-Type') && str_starts_with($request->header('Content-Type'), 'multipart/')) {
    abort_unless($request->all() || $request->allFiles(), 422, 'Malformed multipart body.');
}

Debugging tip. When a PUT multipart request lands empty, log three things at the top of the middleware stack: $request->getMethod(), $request->header('Content-Type'), and $request->getContent() length. If content length is 0, PHP or the proxy dropped the body. If length is > 0 but $request->all() is empty after the middleware runs, the parser didn't recognize the boundary — inspect the raw header for an unquoted or missing boundary.

Extension points are limited by design. This is a single-purpose middleware. If you need to normalize the parsed data (e.g., flatten tags[] from JSON-ish bracket keys), do it in a following middleware or a FormRequest prepareForValidation() hook rather than modifying this package. Forking is often easier than trying to extend it.

Boundary mismatch with test helpers. $this->put('/x', [...]) uses Laravel's testing pipeline; it exercises multipart correctly. If you switch to $this->call('PUT', '/x', [], [], $files) and forget to pass the files array, the request arrives empty and it's not the middleware's fault. Stick to the array-based helper.

Keep an eye on Laravel upgrades. This package targets specific Laravel versions. After upgrading major Laravel versions, run the upload tests first — if $request->all() regresses on PUT multipart, the middleware may need a bump before the rest of the app is trustworthy.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle