How HTTP Works
3xx · Redirection
< HTTP/1.1 303 See Other

303 See Other

Redirect to a different resource using GET. Learn when to use 303 to prevent form resubmission and implement the Post-Redirect-Get pattern.

Reviewed 5 min readintermediate2 sourcesTry itMarkdown
Cacheable
Only with explicit freshness
Client action
GET the Location
Usually sent by
Origin app (after POST)
Spec
RFC 9110 §15.4.4
Often confused with
302, 307
On this page

TL;DR: 303 See Other redirects you to a different URL using GET. Used after form submissions to prevent resubmission on refresh.

A 303 See Other status code tells the client to retrieve the requested resource at a different URL using a GET request, regardless of the original request method. Think of it like submitting a form at a post office—once they process your submission (POST), they hand you a receipt (redirect to GET) instead of making you fill out the form again.

This is the cornerstone of the Post-Redirect-Get (PRG) pattern, preventing the dreaded “Do you want to resubmit the form?” browser warning.

When Does This Happen?

You’ll see a 303 See Other response in these common situations:

1. Form Submission Success

User submits form → Server processes → 303 to success page
POST /checkout → 303 → GET /order/12345/confirmation

2. Post-Redirect-Get Pattern

Prevent duplicate form submissions
POST /create-account → 303 → GET /welcome

3. Upload Completion

File uploaded successfully → Redirect to file details
POST /upload → 303 → GET /files/document-123

4. API Resource Creation

Resource created → Redirect to resource view
POST /api/articles → 303 → GET /api/articles/new-article-id

5. Search Results

Search form submitted → Redirect to results page
POST /search → 303 → GET /search?q=keyword

Example Responses

Form Submission Redirect:

HTTP/1.1 303 See Other
Location: https://example.com/order/12345/confirmation
Content-Type: text/html
Content-Length: 0

Account Creation:

HTTP/1.1 303 See Other
Location: https://app.com/welcome
Set-Cookie: user_id=67890; Path=/; HttpOnly; Secure
Cache-Control: no-cache

<!DOCTYPE html>
<html>
<body>
<h1>Account Created!</h1>
<p>Redirecting to your dashboard...</p>
<p><a href="/welcome">Click here if not redirected</a></p>
</body>
</html>

File Upload Success:

HTTP/1.1 303 See Other
Location: https://storage.example.com/files/doc-abc123
ETag: "upload-complete"
X-Upload-ID: abc123
Content-Type: text/plain

Upload successful. Redirecting to file details...

Real-World Example

Imagine you’re processing a payment on an e-commerce site:

User Submits Payment:

POST /checkout/pay HTTP/1.1
Host: shop.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 156

card_number=4111111111111111&expiry=12/28&cvv=123&amount=99.99

Server Processes and Redirects:

HTTP/1.1 303 See Other
Location: https://shop.com/order/ORD-2026-001/success
Set-Cookie: order_id=ORD-2026-001; Path=/
Cache-Control: no-store, no-cache
Content-Type: text/html

<!DOCTYPE html>
<html>
<head><title>Payment Processed</title></head>
<body>
<h1>Payment Successful!</h1>
<p>Your payment has been processed. Redirecting to confirmation page...</p>
<p>If not redirected, <a href="/order/ORD-2026-001/success">click here</a></p>
</body>
</html>

Browser Automatically Follows with GET:

GET /order/ORD-2026-001/success HTTP/1.1
Host: shop.com
Cookie: order_id=ORD-2026-001

Success Page Response:

HTTP/1.1 200 OK
Content-Type: text/html

<!DOCTYPE html>
<html>
<head><title>Order Confirmation</title></head>
<body>
<h1>Thank You for Your Order!</h1>
<p>Order ID: ORD-2026-001</p>
<p>Amount: $99.99</p>
<p>Confirmation email sent to your address.</p>
</body>
</html>

303 vs Other Redirect Codes

CodeMeaningMethod ChangeUse CaseCaching
303See otherAlways becomes GETAfter POST/PUT/DELETENot cached
302Found (temporary)May change to GETTemporary redirectsShort-term cache
307Temporary redirectPreserves methodTemporary, keep methodNot cached
301Moved permanentlyMay change to GETPermanent redirectsLong-term cache

Important Characteristics

Always Changes to GET:

POST /form-submit
↓
HTTP/1.1 303 See Other
Location: /success
↓
GET /success  ← Always GET, never POST

Prevents Form Resubmission:

Without 303:
User submits form → 200 OK → User refreshes → "Resubmit form?" warning

With 303:
User submits form → 303 redirect → GET success page → User refreshes → Safe GET request

Not Cached by Default:

HTTP/1.1 303 See Other
Location: /result
Cache-Control: no-store  ← Typically not cached

Common Mistakes

❌ Using 302 instead of 303 after POST

POST /form-submit
HTTP/1.1 302 Found  ← Browser behavior is unpredictable
Location: /success  ← Should use 303 for POST-redirect-GET

❌ Not implementing PRG pattern

// Bad: Returning HTML directly after POST
app.post('/submit', (req, res) => {
  processForm(req.body)
  res.render('success') // ← User refresh will resubmit
})

❌ Using 303 for permanent redirects

HTTP/1.1 303 See Other  ← Wrong for permanent moves
Location: /permanently-moved  ← Should use 301

✅ Correct usage

POST /form-submit
HTTP/1.1 303 See Other
Location: https://example.com/success
Cache-Control: no-cache

Getting 303 See Other right

Implement Post-Redirect-Get Pattern:

// Express.js example
app.post('/checkout', async (req, res) => {
  const order = await processOrder(req.body)

  // Redirect with 303 to prevent resubmission
  res.redirect(303, `/order/${order.id}/confirmation`)
})

app.get('/order/:id/confirmation', (req, res) => {
  // Safe to refresh - it's just a GET request
  res.render('confirmation', { orderId: req.params.id })
})

Use Absolute URLs:

HTTP/1.1 303 See Other
Location: https://example.com/success  ✓ Absolute
Location: /success  ✗ Relative (works but not recommended)

Include Helpful Response Body:

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="refresh" content="0;url=/success" />
    <title>Redirecting...</title>
  </head>
  <body>
    <h1>Processing Complete</h1>
    <p>Redirecting to confirmation page...</p>
    <p>If not redirected, <a href="/success">click here</a></p>
  </body>
</html>

Don’t Cache 303 Responses:

HTTP/1.1 303 See Other
Location: /result
Cache-Control: no-store, no-cache
Pragma: no-cache

Advanced Use Cases

RESTful API Resource Creation:

POST /api/users HTTP/1.1
Content-Type: application/json

{"name": "Alice", "email": "alice@example.com"}

→

HTTP/1.1 303 See Other
Location: https://api.example.com/users/12345
Content-Type: application/json

{"message": "User created", "id": 12345}

Search Form Submission:

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

query=best+practices&category=http

→

HTTP/1.1 303 See Other
Location: /search?query=best+practices&category=http

→ Now shareable and bookmarkable URL

Implementation Examples

Express.js:

app.post('/register', async (req, res) => {
  try {
    const user = await createUser(req.body)
    req.session.userId = user.id

    // PRG pattern with 303
    res.redirect(303, `/welcome?name=${encodeURIComponent(user.name)}`)
  } catch (error) {
    res.status(400).render('register', { error: error.message })
  }
})

Django:

from django.http import HttpResponseSeeOther

def submit_form(request):
    if request.method == 'POST':
        # Process form
        form_data = process_form(request.POST)

        # Redirect with 303
        return HttpResponseSeeOther(f'/success/{form_data.id}')

    return render(request, 'form.html')

ASP.NET Core:

[HttpPost]
public IActionResult SubmitOrder(OrderModel order)
{
    var orderId = _orderService.ProcessOrder(order);

    // 303 See Other redirect
    return RedirectToAction("Confirmation", "Order",
        new { id = orderId }, true);
}

PHP:

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $orderId = processOrder($_POST);

    // Send 303 redirect
    header('HTTP/1.1 303 See Other');
    header("Location: /order/$orderId/success");
    header('Cache-Control: no-store, no-cache');
    exit;
}

Try It Yourself

Visit our request builder and see a 303 redirect:

  1. Set method to POST
  2. Set path to /form-submit
  3. Add body data: {"name": "test", "email": "test@example.com"}
  4. Click Send request
  5. Watch the 303 redirect to GET request

Try it with curl

Post a form, then let curl follow the redirect with -L. Do not add -X POST: -X forces the method on the redirected request too, which hides the behavior you are trying to see.

curl -i -L -d 'name=test' https://api.example.com/forms

Example output (illustrative, not captured from a real server):

HTTP/2 303
location: /forms/17

HTTP/2 200
content-type: text/html

After a 303, curl sends the follow-up request as GET and drops the body. Pass --post303 to keep it as POST, which is rarely what you want. Use -v to see the second request line.

Frequently asked questions

What does 303 See Other mean?

303 See Other tells the browser to redirect to a different URL using GET, regardless of the original request method. It is commonly used after form submissions to prevent resubmission when users refresh the page.

What is the difference between 303 and 302?

303 always changes the method to GET, while 302 behavior varies by browser and may preserve the original method. Use 303 after POST/PUT/DELETE when you want a guaranteed GET redirect.

What is the Post-Redirect-Get pattern?

PRG is a web pattern where after processing a POST request, the server responds with 303 redirect to a GET page. This prevents duplicate form submissions when users refresh or use the back button.

When should I use 303 vs 307?

Use 303 when you want the redirect to become a GET request (like after form submission). Use 307 when you need to preserve the original HTTP method (POST stays POST).

Sources

  1. MDN Web Docs: 303 See Otherdeveloper.mozilla.org
  2. RFC 9110: 303 See Otherrfc-editor.org

Keep going

Browse /search