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

Source: https://howhttpworks.com/status-codes/303
Last reviewed: 2026-10-04

> **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**

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

**2. Post-Redirect-Get Pattern**

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

**3. Upload Completion**

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

**4. API Resource Creation**

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

**5. Search Results**

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

## Example Responses

**Form Submission Redirect:**

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

```

**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
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:**

```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
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:**

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

**Success Page Response:**

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

| 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:**

```text
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
HTTP/1.1 303 See Other
Location: /result
Cache-Control: no-store  ← Typically not cached
```

## 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**

```javascript
// 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
HTTP/1.1 303 See Other  ← Wrong for permanent moves
Location: /permanently-moved  ← Should use 301
```

**✅ Correct usage**

```http
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:**

```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
HTTP/1.1 303 See Other
Location: https://example.com/success  ✓ Absolute
Location: /success  ✗ Relative (works but not recommended)
```

**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
HTTP/1.1 303 See Other
Location: /result
Cache-Control: no-store, no-cache
Pragma: no-cache
```

## 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:**

```http
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:**

```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:**

```python
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:**

```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:**

```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](https://howhttpworks.com/tools/playground) 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.

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

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

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

## Related Status Codes

- [302 Found](https://howhttpworks.com/status-codes/302) - Temporary redirect (may or may not change method)
- [307 Temporary Redirect](https://howhttpworks.com/status-codes/307) - Temporary redirect preserving method
- [301 Moved Permanently](https://howhttpworks.com/status-codes/301) - Permanent redirect
- [201 Created](https://howhttpworks.com/status-codes/201) - Resource successfully created
