303 See Other: When Servers Use This Redirect
What the 303 See Other status code means, how it always changes the request method to GET, the Post/Redirect/Get pattern, and how 303 differs from 302 and 307.
A 303 See Other response tells the browser: "I have processed your request. Now go GET this other URL to see the result." It always changes the request method to GET, regardless of what the original method was. If the client sent a POST, the redirect will be a GET. If it sent a PUT, the redirect will be a GET. Always GET.
This is not a general-purpose redirect like 301 or 302. It has a specific job: separating the action a client performs (like submitting a form) from the page the client should see afterward (like a confirmation page). If you have ever submitted an order form and landed on a "Thank you for your order" page, a 303 redirect probably made that transition happen.
For a comparison of all redirect status codes, see our HTTP Redirect Guide and HTTP Redirect Status Codes.
What the spec says
RFC 9110 defines the 303 status code in Section 15.4.4: [1]
The 303 (See Other) status code indicates that the server is redirecting the user agent to a different resource, as indicated by a URI in the Location header field, which is intended to provide an indirect response to the original request.
And the critical behavioral requirement:
A user agent can perform a retrieval request targeting that URI (a GET or HEAD request if using HTTP), which might also be redirected, and present the eventual result as an answer to the original request.
In plain terms: whatever method the client used, the redirect always becomes a GET. The browser should not resend the original request body. It should just load the new page.
The Post/Redirect/Get pattern
The 303 status code exists primarily to enable the Post/Redirect/Get (PRG) pattern. This is one of the most important patterns in web development, and understanding it explains why 303 matters.
Here is the problem PRG solves. A user submits a form:
POST /checkout HTTP/1.1
Host: store.example.com
Content-Type: application/x-www-form-urlencoded
item=widget&quantity=1&card=xxxx-xxxx-xxxx-1234
The server processes the order and responds with a confirmation page. If the server responds directly with the confirmation HTML (a 200 response to the POST), something dangerous happens: the user's browser still considers the current page to be the result of a POST request.
If the user hits the back button and then forward, or refreshes the page, the browser will ask: "Do you want to resubmit the form?" If the user clicks yes (or if the browser does it automatically), the order gets placed again. Duplicate charge. Duplicate shipment. Angry customer.
The PRG pattern prevents this by inserting a 303 redirect between the POST and the confirmation page:
Step 1: Browser sends POST /checkout
Step 2: Server processes the order
Step 3: Server responds with 303 See Other, Location: /order/12345/confirmation
Step 4: Browser sends GET /order/12345/confirmation
Step 5: Server responds with 200 OK and the confirmation HTML
Now the browser's current page is the result of a GET request, not a POST. Refreshing the page just reloads the confirmation. Hitting back and forward does not resubmit the form. The order is safe from accidental duplication.
HTTP/1.1 303 See Other
Location: /order/12345/confirmation
That 303 response is the key. It explicitly tells the browser: "Switch to GET. Do not repeat the POST."
Why not just use 302?
This is a fair question. A 302 redirect after a POST works in most browsers. In practice, every major browser changes the method to GET when following a 302, even though the HTTP specification historically said they should not.
The difference is intent. A 302 says "this resource is temporarily at a different URL." A 303 says "go look at this other resource using GET." They communicate different things to the client, even if the browser behavior ends up looking similar.
Here are the practical reasons to prefer 303 over 302 in the PRG pattern:
Semantic correctness. A 302 technically means "the resource you requested is temporarily located elsewhere." A 303 means "the response to your request can be found at this other URI, and you should GET it." The checkout endpoint did not move. The server is pointing the client to a different resource entirely. That is what 303 communicates.
Guaranteed method change. The 303 specification requires the browser to change the method to GET. With 302, the method change is a historical accident that browsers adopted but that the spec did not originally require. [1]
Clarity for API clients. Non-browser HTTP clients are more likely to follow the spec literally. A 302 response to a POST may cause some clients to resend the POST to the new URL, which could duplicate the action. A 303 unambiguously tells every client to switch to GET.
For a broader comparison of when to use which redirect code, see 301 vs 302 Redirects and Redirect Methods Compared.
How 303 compares to other redirect codes
The full picture of redirect codes along two axes: permanence and method behavior.
May change method Must preserve method
───────────────── ────────────────────
Permanent: 301 Moved Permanently 308 Permanent Redirect
Temporary: 302 Found 307 Temporary Redirect
Always GET: 303 See Other (no equivalent)
303 stands alone in the bottom-left corner. It is the only redirect code that explicitly requires the client to switch to GET. It is always temporary (the redirect is not cached permanently). And it is not really a "this URL has moved" redirect at all. It is more of a "go look over there for the answer."
The key distinctions:
- 303 vs 302: Both are temporary. 302 technically should preserve the method but browsers change it to GET anyway. 303 explicitly requires the change to GET. Use 303 when you want the method change, 302 when you are doing a standard temporary redirect of a GET request.
- 303 vs 307: Both are temporary. 307 must preserve the method (a POST stays a POST). 303 must change the method to GET. They are opposites in terms of method handling.
- 303 vs 301: 301 is permanent and may change the method. 303 is temporary and always changes the method. They serve completely different purposes.
When web applications use 303
After form submissions (PRG)
The primary use case, covered in detail above. Any form that creates or modifies data on the server should use the PRG pattern with a 303 redirect.
# Django example
from django.http import HttpResponseRedirect
def checkout(request):
if request.method == 'POST':
order = process_order(request.POST)
# 303 See Other - redirect to confirmation page
response = HttpResponseRedirect(f'/order/{order.id}/confirmation')
response.status_code = 303
return response
After login
Many web applications redirect to a dashboard or home page after successful login. Since the login form is a POST, a 303 redirect ensures the browser loads the dashboard with a GET and does not try to re-POST login credentials on refresh.
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded
username=jane&password=secret
HTTP/1.1 303 See Other
Location: /dashboard
After file uploads
Upload a file via POST, then redirect to a page showing the uploaded file or a list of files. The 303 ensures the browser does not try to re-upload the file on refresh.
After DELETE operations in web UIs
When a web UI sends a DELETE request (or a POST that performs a deletion) and the user should see an updated list page afterward:
POST /admin/posts/456/delete HTTP/1.1
HTTP/1.1 303 See Other
Location: /admin/posts
The user lands on the posts list (via GET), which now shows the updated list without the deleted post.
Implementation examples
Node.js / Express
app.post('/checkout', (req, res) => {
const order = processOrder(req.body);
res.redirect(303, `/order/${order.id}/confirmation`);
});
Express's res.redirect() accepts the status code as the first argument. Using 303 here explicitly tells the browser to GET the confirmation page.
PHP
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$orderId = processOrder($_POST);
header('HTTP/1.1 303 See Other');
header('Location: /order/' . $orderId . '/confirmation');
exit;
}
?>
Python / Flask
from flask import Flask, redirect, request
app = Flask(__name__)
@app.route('/checkout', methods=['POST'])
def checkout():
order = process_order(request.form)
return redirect(f'/order/{order.id}/confirmation', code=303)
Raw HTTP response
At the protocol level, the response is straightforward:
HTTP/1.1 303 See Other
Location: /order/12345/confirmation
Content-Length: 0
No response body is required. The Location header is the only thing the browser needs.
Caching behavior
303 responses are not cached by default. [1] This makes sense: a 303 is a response to a specific action (like a form submission), not a statement about where a resource lives. Each time the client performs the action, the server should process it and respond with a fresh 303 pointing to the appropriate result.
If you want a 303 response to be cached (unusual, but possible), you need to include explicit caching headers:
HTTP/1.1 303 See Other
Location: /status-page
Cache-Control: max-age=60
In practice, caching a 303 is rare. The normal pattern is that each POST gets its own 303 response with a unique location.
SEO implications
303 redirects do not transfer ranking signals. Google treats 303 as a temporary redirect, similar to 302. [2] The search engine will not replace the original URL with the redirect target in its index.
This is the correct behavior because 303 is not used for URL changes. It is used for flow control within an application. The checkout URL stays in the sitemap. The order confirmation page is a transient result, not a replacement for the checkout page.
If you need a redirect that transfers SEO equity, use a 301 redirect for permanent moves or a 302 for temporary ones. 303 is not a tool for URL management or SEO.
Common mistakes
Using 303 for page moves
A 303 should not be used to indicate that a page has moved to a new URL. That is what 301 (permanent) and 302 (temporary) are for. Using 303 to redirect /old-page to /new-page is semantically wrong, even though the browser will follow it. It tells clients "go GET this other resource as a response to your request" rather than "this page has moved."
Using 302 where 303 belongs
Many frameworks default to 302 for all redirects. If you are redirecting after a POST and want the browser to switch to GET, explicitly use 303. The 302 happens to work in browsers, but 303 communicates your intent clearly and behaves correctly with all HTTP clients.
Forgetting 303 in APIs
API developers sometimes respond to POST requests with a 200 and a body that says "go look at /resource/123." A 303 response with a Location header does this at the protocol level, letting HTTP clients handle the redirect automatically rather than requiring custom response parsing.
When in doubt: POST then 303
Any time your server processes a POST that changes data and the user should see a result page, use 303 See Other. It prevents duplicate submissions, works correctly with browser navigation (back/forward/refresh), and communicates your intent to every HTTP client.
The bottom line
303 See Other is not a general redirect. It is a flow control mechanism. It tells the client: "I handled your request. Now go GET this URL to see the result." It exists to make the Post/Redirect/Get pattern work correctly, preventing duplicate form submissions and keeping browser history clean. Use it after any POST that modifies data. Use 301 or 302 for actual URL redirects.
References
- IETF, "RFC 9110 - HTTP Semantics, Section 15.4.4: 303 See Other," June 2022. https://httpwg.org/specs/rfc9110.html#status.303
- Google, "Redirects and Google Search," Google Search Central, 2024. https://developers.google.com/search/docs/crawling-indexing/301-redirects
Never miss a broken redirect
Trace redirect chains and detect issues before they affect your users and SEO. Free instant tracing.
Try Redirect Tracer