HTTP

Status Code

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.

5 min read intermediate Try in Playground

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

```text

**Account Creation:**

```http
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...
```text

## Real-World Example

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

**User Submits Payment:**

```http
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>
```text

**Browser Automatically Follows with GET:**

```http
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>
```text

## 303 vs Other Redirect Codes

| Code    | Meaning            | Method Change      | Use Case               | Caching          |
| ------- | ------------------ | ------------------ | ---------------------- | ---------------- |
| **303** | See other          | Always becomes GET | After POST/PUT/DELETE  | Not cached       |
| **302** | Found (temporary)  | May change to GET  | Temporary redirects    | Short-term cache |
| **307** | Temporary redirect | Preserves method   | Temporary, keep method | Not cached       |
| **301** | Moved permanently  | May change to GET  | Permanent redirects    | Long-term cache  |

## Important Characteristics

**Always Changes to GET:**

```http
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
```text

## Common Mistakes

**❌ Using 302 instead of 303 after POST**

```http
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
})
```text

**❌ Using 303 for permanent redirects**

```http
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
```javascript

## Best Practices

**Implement Post-Redirect-Get Pattern:**

```javascript
// 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)
```text

**Include Helpful Response Body:**

```html
<!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
```text

## Advanced Use Cases

**RESTful API Resource Creation:**

```http
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
```javascript

## Implementation Examples

**Express.js:**

```javascript
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')
```javascript

**ASP.NET Core:**

```csharp
[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

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).

Keep Learning