WEB / FIELD NOTES

Why the request after a POST redirect is sometimes a GET

A fictional POST /orders followed after 301 or 302 becomes a GET with no body. 303 makes that change on purpose. 307 and 308 keep the method.

The order URL is https://shop.example/orders. The method is POST, Content-Type is application/json, and the body is {"sku":"MUG-1","qty":1}. If the response is 302 and Location is https://shop.example/orders/1001, a browser that follows that URL sends the next request as a GET with no body. The same Location with 307 keeps the next request as POST. shop.example, MUG-1, and quantity 1 are fictional. This article was not written by sending that request or collecting a browser log.

The boundary is RFC 9110 and the Fetch standard as read on 2026-09-26. RFC 9110 says what a user agent may change. Fetch states the steps a browser follows when the redirect mode is follow. Unless set otherwise, that mode is follow. A client that does not implement Fetch, such as curl, can keep the method for the same status code.

Permanent, temporary, and the method are different questions

301 Moved Permanently means the target resource has a new permanent URI, and later references ought to use it. 308 Permanent Redirect is also permanent. 302 Found and 307 Temporary Redirect mean the resource is elsewhere for now, so later requests ought to keep using the original URI.

301 and 302 carry a historical exception. A user agent may change the next method from POST to GET. When that change is not wanted, use 308 for a permanent move and 307 for a temporary one. 307 and 308 must not change the method on an automatic redirect.

303 See Other is not a notice that the same resource has moved. The Location URI is not equivalent to the original target, and a user agent can retrieve it with GET or HEAD. The usual purpose is to point at the result of a POST, at an address that can be bookmarked and cached.

301 and 308 are on the heuristic cache list. 302, 303, and 307 are not. A status outside that list can still be stored when freshness is explicit, as with Cache-Control. A POST response follows the method rule first. It is cacheable only with explicit freshness and a Content-Location equal to the POST URI, and even then a stored response can satisfy a later GET or HEAD. A 301 does not by itself store the POST response.

301 is permanent and may change POST to GET. 302 is temporary and allows the same change. 303 retrieves a different resource with GET or HEAD. 307 is temporary and 308 is permanent, and an automatic follow keeps the method. Only 301 and 308 are on the heuristic cache list.
Permanent or temporary, whether the method changes, and the heuristic cache list are three separate questions.

When a browser follows, a 302 POST becomes GET

With follow, Location is resolved against the URL of the request that received the response, then requested again. error is a network error. manual does not follow unless the request is a navigation. An automatic redirect of a method that is not safe needs care, but the fetch default is still follow.

When the status is 301 or 302 and the method is POST, Fetch sets the method to GET and the body to null. Following the 302 above produces GET https://shop.example/orders/1001 with no body. The product code and quantity are not on that request.

The same Location on 307 or 308 does not enter that condition. The method stays POST, and the body is attached again when it can be reread. Fetch follow applies the change RFC 9110 allows for 301 and 302. That allowance is not a requirement for every client.

The fictional POST https://shop.example/orders has body sku MUG-1 and quantity 1. Following 302 Location /orders/1001 becomes GET, and the body and Content-Type are gone. The same Location with 307 keeps POST and the body.
The same fictional order does not make the same next request after 302 and after 307.

303 is the GET that shows the result

303 is not limited to POST. If the method is neither GET nor HEAD, Fetch sets it to GET and drops the body. A PUT or DELETE followed on 303 is also a GET.

301 and 302 are narrower. Fetch changes only POST to GET for those two codes. A fictional PUT https://shop.example/orders/1001 that receives 302 to another URL stays PUT under follow.

A 303 Location is not the same place as the original resource. After a POST that creates an order, a 303 to /orders/1001 points at the result, and opening that page again does not repeat the POST. A 302 can produce a similar method change, but it is not defined as showing a different resource.

A 303 Location of https://shop.example/orders/1001 is not equivalent to the original URL, and the next request is a GET with no body. 302 is a temporary move of the same resource, and changing POST to GET is a historical allowance. Fetch changes only POST to GET for 301 and 302. 303 also turns PUT and DELETE into GET.
Use 303 to show the result. Use 307 or 308 to keep the POST.

What remains of the headers and the body

When the method becomes GET or HEAD, Fetch removes Content-Encoding, Content-Language, Content-Location, and Content-Type. RFC 9110 also says to remove content-specific fields such as Content-Length and Digest. The two lists of names are not the same. In this fictional example, Content-Type application/json is removed.

A change of origin also removes Authorization. Going from https://shop.example to https://pay.example drops that name at this step. A same-origin /orders/1001 does not remove Authorization here. Cookies can be attached later according to the credentials mode, so they are not treated like Authorization. The separate case of a cross-origin JSON request being blocked is covered in Why a CORS error can mean the server never saw the POST.

If the status is not 303, the body is present, and that body cannot be reread, Fetch returns a network error. Following a 307 with a stream that can be read only once can fail. 303 drops the body, so it skips that check. The redirect count starts at zero, and the redirect after 20 followed responses is a network error. An older recommendation was a maximum of five, and some clients may still use a fixed limit. A Location that is not http or https is not followed.

When the method becomes GET, Fetch removes Content-Encoding, Content-Language, Content-Location, and Content-Type. A move from shop.example to pay.example drops Authorization, while the same origin keeps it. Except for 303, the body must be rereadable, and the redirect after 20 follows is an error.
A new method drops the body headers. A new origin drops Authorization.

Do not choose the next request from the status alone

On a log, write down the first status and Location, the second method and whether it had a body, and the origins of the two URLs. A second request that is GET does not by itself mean the server rejected the POST. These branches are the procedure in the standards, not a browser run of the fictional URL.

END OF NOTEBack to the library
COMMENTS BOX

Add your perspective.

Share a question, another approach, or something you have tried.

Newest first

Checking sign-in…

Loading comments…