Jonathan Bird Web Development

How to Fix "The GET Method Is Not Supported for This Route" Error in Laravel 13 (2026 Guide)

Last updated: July 26, 2026

If you've been building with Laravel for more than a week, you've almost certainly hit "The GET method is not supported for this route. Supported methods: POST." at some point. It's one of the first errors every Laravel developer meets, and it keeps turning up in production apps too: after a refactor, after a deploy, or after a browser does something sneaky with a redirect behind your back.

In this guide, I'll walk you through what causes the "GET method is not supported" error (and its POST, PUT, and DELETE siblings), how to diagnose it, and the various ways to fix it in Laravel 13. Whether it's a form, a logout link, an API call, or a redirect gone wrong, I've got you covered in this article.

What is the "GET Method Is Not Supported" Error?

This error is thrown by Laravel's router when the URL you requested matches a registered route, but the HTTP method you used does not. The exception class is Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException, it returns a 405 Method Not Allowed status code, and the message looks like this in Laravel 13:

1The GET method is not supported for route profile. Supported methods: POST.

Older Laravel versions phrase it slightly differently, which is why you'll see both variants around the internet:

1The GET method is not supported for this route. Supported methods: POST.

The important thing to understand is that this is not a "route not found" error. Laravel found a route at that URI just fine. It's telling you the route exists, but only for the verbs listed after "Supported methods". If no route matched the URI at all, you'd normally get a 404 instead (one caveat: a catch-all like Route::get('/{slug}') or Route::fallback() matches almost any URI, so wrong-verb requests to nonexistent URLs turn into 405s rather than 404s once one exists). So the error message is actually handing you the answer: the request arrived as GET, and the route only accepts POST (or whatever verbs it lists).

That means there are only ever two ways to fix it:

  1. Change the request so it uses a verb the route supports
  2. Change the route so it supports the verb being used

Almost every fix below is a variation of one of those two.

Common Causes of the Method Not Supported Error

Visiting a POST URL Directly in the Browser

Typing a URL into the address bar, clicking a bookmark, or hitting refresh always sends a GET request. If that URL only has a POST route (a form submission endpoint, for example), you'll get this error. This is easily the most common cause, and it often shows up when a user refreshes the page after submitting a form, or when a crawler follows a URL it found somewhere.

The Form Method Doesn't Match the Route

An HTML form with no method attribute submits as GET:

1{{-- No method attribute, so this submits as GET --}}
2<form action="/contact">
3 ...
4</form>

If /contact is registered with Route::post(), the submission blows up. The reverse happens too: a method="POST" form pointed at a GET-only route gives you "The POST method is not supported".

HTML Forms Can't Send PUT, PATCH, or DELETE

This one catches everyone at least once. HTML forms only support GET and POST. If your route is defined with Route::put(), Route::patch(), or Route::delete() (which is exactly what Route::resource() generates for update and destroy actions), a plain HTML form can't reach it without method spoofing. A method="PUT" attribute on a form is invalid HTML and the browser silently submits as GET, which is why the error message so often says the GET method isn't supported when you were sure you were sending a PUT.

The classic. The starter kits register logout as a POST route (for good reason: you don't want a crawler or a prefetching browser logging your users out by following a link). Then someone adds a plain <a href="/logout"> link in a nav bar, a user clicks it, the browser sends GET, and you get "The GET method is not supported for route logout. Supported methods: POST."

A Redirect Silently Turned Your POST Into a GET

This is the sneaky one that shows up in production but not locally. When a browser follows a 301 or 302 redirect of a POST request, it re-issues the request as GET. So if your form posts to http://example.com/subscribe and the server 301-redirects to https://example.com/subscribe (or from non-www to www, or adds a trailing slash), the redirected request arrives as a GET and the POST body is gone. Your route is fine, your form is fine, and the error still fires.

Only 307 and 308 redirects preserve the original method and body. If your forms work locally but 405 in production, check for a web server redirect between the form and the route.

Posting to the Wrong Path

Routes in routes/api.php are prefixed with /api by default. If you define Route::post('/orders', ...) in api.php but your frontend posts to /orders instead of /api/orders, the request can match a completely different web route that only supports GET, and the error message will look baffling until you notice the path.

A Catch-All Route Is Swallowing the URL

If your app has a CMS-style catch-all near the bottom of routes/web.php, it changes how wrong URLs fail:

1Route::get('/{page}', [PageController::class, 'show']);

That URI pattern matches almost anything. So a POST to a mistyped URL (a form action with a typo, an old integration hitting a removed endpoint) no longer 404s; it matches the catch-all's URI, fails on the verb, and throws "The POST method is not supported... Supported methods: GET, HEAD." The same applies to resource wildcards: with GET /posts/{post} registered, a POST to /posts/anything 405s rather than 404ing. If the supported methods in the message look unrelated to the route you thought you were hitting, a wildcard has captured the URL. Related rule worth knowing: routes are matched top to bottom, so specific routes must be declared before wildcards or the wildcard wins.

A Stale Route Cache

If you've changed route definitions but production is still running an old php artisan route:cache file, Laravel resolves requests against the cached routes, not the ones in your files. A route you just changed from GET to POST can keep rejecting POST requests until the cache is rebuilt.

How to Fix the Method Not Supported Error

Fix 1: Check What the Route Actually Supports

Before changing anything, find out what Laravel thinks the route accepts. The error message tells you the supported methods, and route:list tells you the full picture:

1php artisan route:list --path=profile

The output shows the verb(s) next to each route:

1POST profile ................ ProfileController@update
2GET profile/edit ........... ProfileController@edit

Now you know which side to fix: the request or the route.

Fix 2: Match the Form Method to the Route

For routes registered with Route::post(), make sure the form says so explicitly:

1<form action="{{ route('contact.store') }}" method="POST">
2 @csrf
3 
4 ...
5 
6 <button type="submit">Send</button>
7</form>

Don't forget @csrf while you're in there. A missing CSRF token gives you a different error (a 419), which I've covered in detail in my 419 Page Expired guide.

Fix 3: Use @method for PUT, PATCH, and DELETE Routes

Since HTML forms only support GET and POST, Laravel uses a hidden _method field to "spoof" the other verbs. The form submits as POST, and Laravel routes it as whatever _method says. The @method Blade directive generates the field for you:

1<form action="{{ route('posts.update', $post) }}" method="POST">
2 @csrf
3 @method('PUT')
4 
5 ...
6 
7 <button type="submit">Update</button>
8</form>

And for deletes:

1<form action="{{ route('posts.destroy', $post) }}" method="POST">
2 @csrf
3 @method('DELETE')
4 
5 <button type="submit">Delete</button>
6</form>

The form's method attribute must stay POST. Writing method="PUT" directly is invalid HTML and the browser will fall back to GET, landing you right back on this error.

This matters for JavaScript too. If you're uploading files with FormData, PHP doesn't parse multipart/form-data on a real PUT request automatically (and Laravel doesn't fill that gap), so the standard approach is to send a POST with _method spoofing:

1const formData = new FormData();
2formData.append('_method', 'PUT');
3formData.append('avatar', file);
4 
5fetch(`/users/${userId}`, {
6 method: 'POST',
7 headers: {
8 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
9 },
10 body: formData,
11});

Inertia has the same convention with router.post('/users/1', { _method: 'put', ... }).

A logout link needs to submit a POST request. The simplest accessible version is a tiny form styled to look like a link:

1<form action="{{ route('logout') }}" method="POST">
2 @csrf
3 <button type="submit">Log out</button>
4</form>

If your markup really needs an anchor tag, have it submit a hidden form:

1<a href="{{ route('logout') }}"
2 onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
3 Log out
4</a>
5 
6<form id="logout-form" action="{{ route('logout') }}" method="POST" class="hidden">
7 @csrf
8</form>

Whatever you do, resist the temptation to change the logout route to GET. Browsers prefetch links, crawlers follow them, and a GET logout means users get logged out by things that aren't clicks.

Fix 5: Redirect After POST (and Don't Redirect To a POST)

Two rules keep the browser out of trouble:

After handling a POST, always redirect to a GET route. This is the Post/Redirect/Get pattern, and it's why a refresh after form submission shouldn't resubmit or 405:

1public function store(StoreContactRequest $request): RedirectResponse
2{
3 Message::create($request->validated());
4 
5 return redirect()->route('contact.thanks');
6}

Never redirect to a URL that only accepts POST. redirect() sends a 302, the browser follows it with GET, and you've manufactured this exact error. Redirect targets should always be GET routes.

One nuance for XHR and SPA stacks: 301 and 302 only rewrite POST to GET. A redirect issued in response to a PUT, PATCH, or DELETE is followed with the same verb, so redirecting a DELETE to a GET-only page 405s all over again. The fix is a 303 See Other response, which forces the follow-up request to be a GET:

1return redirect()->route('posts.index')->setStatusCode(303);

Inertia's Laravel adapter converts 302s to 303s after these verbs automatically, which is exactly why its docs insist on 303s.

If the redirect is happening at the web server level (http to https, non-www to www, trailing slashes), either point your forms at the final canonical URL, or make the server use a 308 redirect, which preserves the method and body:

1# nginx: preserve the method when forcing https
2return 308 https://example.com$request_uri;

The cleanest fix is making sure APP_URL and every form action already use the canonical scheme and host, so no redirect ever sits between the form and the route.

Fix 6: Send the Right Verb from API Clients

For APIs, match the client verb to the route definition rather than spoofing. All of these are equivalent to what the route expects for Route::put('/api/orders/{order}', ...):

1// fetch
2fetch(`/api/orders/${id}`, {
3 method: 'PUT',
4 headers: {
5 'Content-Type': 'application/json',
6 'Accept': 'application/json',
7 },
8 body: JSON.stringify(payload),
9});
10 
11// axios
12axios.put(`/api/orders/${id}`, payload);

And from Laravel's own HTTP client, if you're calling another service:

1use Illuminate\Support\Facades\Http;
2 
3$response = Http::put("https://api.example.com/orders/{$id}", $payload);

While you're checking the verb, check the path too. A route defined in routes/api.php lives under /api/... by default, so the client needs to call /api/orders, not /orders.

Fix 7: Register Multiple Verbs When a Route Genuinely Needs Them

Occasionally a route legitimately needs to answer more than one verb (a webhook endpoint that a third party pings with GET for verification and POST for payloads, for example). Use Route::match for a specific set, or Route::any for everything:

1use Illuminate\Support\Facades\Route;
2 
3Route::match(['get', 'post'], '/webhooks/provider', [WebhookController::class, 'handle']);

I'd treat this as the exception rather than the rule. Separate verbs to separate actions keeps controllers easier to reason about, and Route::any in particular tends to hide bugs that a 405 would have surfaced early.

Fix 8: Clear a Stale Route Cache

If the route definitions look right but the error persists (especially right after a deploy), rebuild the route cache:

1php artisan route:clear

Or clear every cache in one hit:

1php artisan optimize:clear

In production, your deploy script should run php artisan optimize (which includes route:cache) after every release, so the cache always matches the code that's live.

Debugging the Error

Read the Message Properly

The message contains both halves of the diagnosis: the verb that arrived, and the verbs the route accepts. "The GET method is not supported for route logout. Supported methods: POST." tells you a GET request hit /logout and only POST is registered. Half the battle is just believing what it says, then working out why the request went out with that verb.

Check the Actual Request in DevTools

Open the Network tab, submit the form (or click the link), and look at the request Laravel actually received:

  • Request Method shows the real verb, regardless of what you intended
  • A 301/302 entry directly before the failing request is the smoking gun for the redirect-eats-POST problem
  • The Payload tab shows whether _method was included for spoofed requests

Test the Route Directly with cURL

Take the browser out of the equation and hit the route with an explicit verb:

1curl -i -X POST https://example.com/subscribe -d "[email protected]"

If cURL succeeds with POST while your form fails, the route is fine and the problem is in the form or a redirect. Use -L deliberately (cURL doesn't follow redirects by default), and watch how the method changes if a redirect is in play.

List the Routes for That Path

1php artisan route:list --path=orders

You'll see every verb registered for the path, along with middleware and route names. If two route files both claim similar paths, this is where the collision becomes obvious.

Special Cases

Returning JSON 405s for APIs

By default an API consumer that uses the wrong verb gets the standard exception response. If you want a consistent JSON shape for 405s, register a renderer in bootstrap/app.php:

1use Illuminate\Foundation\Configuration\Exceptions;
2use Illuminate\Http\Request;
3use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
4 
5->withExceptions(function (Exceptions $exceptions): void {
6 $exceptions->render(function (MethodNotAllowedHttpException $e, Request $request) {
7 if ($request->is('api/*')) {
8 return response()->json([
9 'message' => 'This endpoint does not support the '.$request->method().' method.',
10 ], 405);
11 }
12 });
13})

HEAD Requests in Your Logs

Uptime monitors and crawlers often send HEAD requests. Laravel automatically answers HEAD on any GET route, but a HEAD request to a POST-only URI will 405 and can fill your logs with noise. That's usually a monitor misconfiguration rather than an application bug: point the monitor at a GET route (the built-in /up health route is ideal).

When the 405 Isn't Coming From Laravel

If the request works locally but 405s in production, look closely at the error page itself. Laravel's 405 renders as an exception page (or your JSON shape); nginx's own "405 Not Allowed" page is plain and branded, and it means the request never reached Laravel at all.

The two usual culprits:

  • nginx serving the URL as a static file. nginx doesn't allow POST to static content, so a POST to a path that resolves to a real file (or to a page captured by a static cache) gets nginx's 405 before PHP is involved. The fix is the standard try_files $uri $uri/ /index.php?$query_string; rule, and excluding POST endpoints from any full-page caching layer
  • A WAF or hardened shared host blocking verbs. ModSecurity rules and some hosting firewalls block PUT, DELETE, or OPTIONS outright. If those verbs fail while GET and POST work, ask the host to relax the rule, or fall back to POST with _method spoofing, which sails through because it's an ordinary POST

Knowing which layer produced the error page saves you from debugging routes that were never the problem.

Livewire: "The POST Method Is Not Supported for Route livewire/update"

Livewire 3 sends every component interaction as a POST to /livewire/update, so a 405 on that route means the request didn't come from Livewire's JavaScript at all. The usual story is that livewire.js failed to load, so a wire:submit form fell back to a native browser submission. Check the browser console for a 404 on the Livewire script, make sure your layout allows Livewire to inject its assets (or calls @livewireScripts explicitly), and if the app is served from a subdirectory, publish the Livewire config and set asset_url so the script resolves correctly. A stale route cache after a Livewire upgrade can produce the same 405, so php artisan optimize:clear is worth running here too.

The Error Appears During Tests

If a feature test throws MethodNotAllowedHttpException, the test is calling the wrong helper for the route:

1// Route::put('/posts/{post}', ...) needs put(), not post()
2$response = $this->put(route('posts.update', $post), $data);

The $this->post(), $this->put(), $this->patch(), and $this->delete() helpers send real verbs, so no _method spoofing is needed in tests.

Everything 405s After Switching Auth Scaffolding

Starter kits differ in how they register auth routes (some use POST /logout, others historically used DELETE). If you've swapped scaffolding or upgraded between kits, run php artisan route:list --path=logout and update your forms to match, rather than assuming the old verb still applies. If the failure you're seeing is a redirect to a missing login route instead, that's a different error I've covered in my Route [login] not defined guide.

Summary

"The GET method is not supported for this route" means the URI matched but the verb didn't, and the fix is always to align the two. Here's a quick checklist:

  1. Run php artisan route:list --path=... to see which verbs the route accepts
  2. Give forms an explicit method="POST" and point them at the right route
  3. Use @method('PUT'/'PATCH'/'DELETE') with a POST form for resource update and destroy routes
  4. Make logout a POST form, not a link
  5. Redirect to GET routes only, and watch for server redirects (http to https, www) that turn POST into GET
  6. Match API client verbs to the route and remember the /api prefix
  7. Clear the route cache with php artisan route:clear after route changes
  8. Check whose error page it is: an nginx-branded 405 or a WAF block means the request never reached Laravel, and a catch-all route can turn what should be 404s into 405s

Once you've internalised the "URI matched, verb didn't" mental model, this error goes from confusing to self-explanatory. The message literally lists the verbs that would have worked.


Having trouble with routing, forms, or other Laravel issues? I specialise in Laravel development and debugging complex application issues. Get in touch to discuss your project.

Topics

Related services

Syntax highlighting by Torchlight

More articles

How to Fix "SQLSTATE[HY000] [2002] Connection Refused" Error in Laravel 13 (2026 Guide)

The "SQLSTATE[HY000] [2002] Connection refused" error means nothing was listening at the host and port Laravel tried to connect to. This guide covers all the common causes and how to fix them in Laravel 13, including localhost vs 127.0.0.1, Docker and Sail service names, custom ports, stale config caches, and CI pipelines.

Read article

How to Hire a Laravel Developer in 2026 (Complete Guide)

How to hire a Laravel developer in 2026: what to look for, the questions to ask, the red flags to avoid, and what it actually costs in Australia, from a developer shipping production Laravel since 2013.

Read article

Your website should be a business asset, not a headache

For over 15 years I've helped Australian enterprise, government, and not-for-profit organisations ship websites that are fast, secure, and accessible. Get in touch to discuss your project today.