HTTP 303 See Other

See Other

HTTP 303 is See Other: the answer to this request is at another URL, given in Location, and the client should fetch it with GET (or HEAD) whatever method it used (RFC 9110 section 15.4.4). It’s the redirect for “done, now look over there”. The classic use is sending a browser from a form’s POST to a page that shows the result.

Post/Redirect/Get

POST /orders HTTP/1.1
Content-Type: application/x-www-form-urlencoded

item=42&qty=1

HTTP/1.1 303 See Other
Location: /orders/1017

The browser follows with GET /orders/1017. Reloading that page repeats the GET, not the order, and the back button doesn’t offer to resubmit the form.

303, 302 or 307

Browsers follow the Fetch standard, which changes the method like this:

301, 302    POST becomes GET; PUT, PATCH and DELETE are kept
303         every method except GET and HEAD becomes GET
307, 308    the method and the body are kept

A 302 works after a classic form POST, but after a fetch() with DELETE or PUT it sends the same DELETE or PUT to the new URL. 303 is the only redirect that always lands on a GET. Rails’ docs recommend it for that case, and its scaffolds answer a destroy with status: :see_other. Outside browsers, RFC 9110 lets a client keep POST on a 302, one more reason to say 303 when you mean it.

Gotchas

  • FastAPI’s RedirectResponse defaults to 307, which makes the browser repeat the POST at the new URL, often into a 405. Pass status_code=303.
  • Express’s res.redirect() defaults to 302. Fine for form POSTs; pass 303 for anything a script might send with another method.
  • curl -L turns a POST into a GET on a 303, unless you pass --post303. But with -X POST, curl uses POST for every request it makes, the followed one included; send the body with -d and drop -X.
  • Caching. A 303 isn’t heuristically cacheable; it’s stored only with explicit freshness. The page it points to is cached on its own terms.
  • On a GET, RFC 9110 reads a 303 as “there’s no representation of this, but here’s a resource that describes it”. Linked Data uses it that way for URIs that name things rather than documents.

In your stack

res.redirect(303, `/orders/${order.id}`);                          // Express
redirect_to order_path(@order), status: :see_other                 # Rails
return RedirectResponse(f"/orders/{order.id}", status_code=303)    # FastAPI
return redirect(url_for("order", id=order.id), code=303)           # Flask
http.Redirect(w, r, "/orders/1017", http.StatusSeeOther)

Search 302 303 307 in the tool below to compare the three.

Try it in the HTTP Status Codes

303 See Other

  1. 303See Other3xx Redirection

    The answer to the request is at another URL and must be fetched with GET, whatever method got you here.

    When to use it

    Post/Redirect/Get: a form post that finishes by sending the browser to the result page, so a refresh does not submit it again.

    Common mistakes

    Using a 302 for that job and letting some clients resubmit the POST, and putting the result in the redirect body where nobody reads it.

    Not cacheable by defaultRFC 9110

Every code, by class

Codes marked unofficial come from one server’s own convention — nginx, Cloudflare, cPanel, Microsoft — and are not in the IANA registry.

Runs in your browser. Nothing you enter is sent anywhere.