# PUT vs POST: Create or Replace a Resource

> PUT vs POST in REST APIs: who picks the URL, idempotency, retries, upsert, and raw requests showing when to create with POST and when to replace with PUT.

Source: https://howhttpworks.com/compare/put-vs-post
Last reviewed: 2026-10-04

> **TL;DR:** Use PUT when the client knows the exact URL of the resource and sends its complete new state; repeating it is harmless. Use POST when the server chooses the URL (create in a collection) or when the operation is an action, not a replacement. For partial edits use PATCH instead (see [PUT vs PATCH](https://howhttpworks.com/compare/put-vs-patch)).

## Side By Side

| | PUT | POST |
|---|---|---|
| Who picks the URL | The client | The server (for creates) |
| Target of the request | The resource itself, `/users/42` | A collection or handler, `/users` or `/users/42/reset` |
| Semantics | Replace the state at this URL with the body | Process the body according to the resource's own rules |
| Idempotent | Yes (RFC 9110 section 9.2.2) | No |
| Safe | No | No |
| Automatic retry by clients/proxies | Allowed after a connection failure | Not done automatically |
| Partial update | No, send the full state | Whatever you define, but PATCH exists for this |
| Success status | 201 if created, 200 or 204 if replaced | 201 with `Location` if created, otherwise 200 or 204 |
| Cacheable response | Not cacheable, and invalidates cached GETs of that URL | Rarely cacheable, and unsafe methods invalidate cached copies of the target URL |

## Which One Should I Use

### The server generates the id: POST

```http
POST /api/orders HTTP/1.1
Host: api.example.com
Content-Type: application/json

{"sku": "TSHIRT-M", "qty": 2}
```

```http
HTTP/1.1 201 Created
Location: /api/orders/ord_5521
Content-Type: application/json

{"id": "ord_5521", "sku": "TSHIRT-M", "qty": 2}
```

Send the same request twice and you get two orders, `ord_5521` and `ord_5522`. That is the correct behavior for POST, and the reason it needs extra care on flaky networks.

### The client owns the identifier: PUT

```http
PUT /api/users/alice/preferences HTTP/1.1
Host: api.example.com
Content-Type: application/json

{"theme": "dark", "locale": "en-GB", "emails": false}
```

```http
HTTP/1.1 200 OK
Content-Type: application/json

{"theme": "dark", "locale": "en-GB", "emails": false}
```

Sending that again leaves the preferences exactly the same. A mobile client that lost its connection mid-request can retry without asking whether the first attempt landed. If `alice` had no preferences document yet, the server returns `201 Created` for the same request: that is a legitimate upsert.

### Uploading to a known key: PUT

Object stores such as S3 use PUT because the caller names the key.

```http
PUT /uploads/2026/report.pdf HTTP/1.1
Host: bucket.example.com
Content-Type: application/pdf
Content-Length: 48211
```

### Actions that are not state replacement: POST

`POST /orders/ord_5521/cancel`, `POST /payments/pay_1/refund`, `POST /search` with a large filter body. None of these is "set the state of this URL to the body", so PUT would lie.

### Retrying safely

For PUT, retry freely. For POST, add an idempotency key so the server can deduplicate:

```http
POST /api/payments HTTP/1.1
Host: api.example.com
Content-Type: application/json
Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9324

{"amount": 4200, "currency": "usd"}
```

The server stores the response under the key and replays it if the same key arrives again. Without it, a POST that timed out leaves the client unable to tell whether the charge happened.

## Common Mistakes

**Using PUT with a partial body.** Clients send `{"theme": "dark"}` to `PUT /users/alice/preferences` and the server wipes the other fields, or worse, keeps them and behaves like PATCH without saying so. Pick PATCH for partial updates or require the full document.

**Using POST for everything.** It works, but you lose idempotency guarantees, and proxies and clients cannot reason about retries.

**Making PUT non-idempotent.** A PUT handler that appends to a list or increments a counter breaks the contract. Retries, and any middleware that replays on failure, then corrupt data.

**Returning 200 for every create.** Return 201 so clients can distinguish a create from a replace, and so `Location` gets used.

**Letting the client mint ids for PUT without validating them.** If the URL is `PUT /users/{id}` and `{id}` is attacker-supplied, you need to authorize overwrite as well as create, or one tenant can overwrite another's record.

**Expecting PUT from an HTML form.** Browsers will not send it; use a framework override field or fetch().

Related: [GET vs POST](https://howhttpworks.com/compare/get-vs-post), [the idempotent glossary entry](https://howhttpworks.com/glossary/idempotent), [POST method reference](https://howhttpworks.com/methods/post), [PUT method reference](https://howhttpworks.com/methods/put).

## FAQ

### Should I use PUT or POST to create a resource?

Use POST to a collection (POST /orders) when the server assigns the id. Use PUT to a specific URL (PUT /users/alice/avatar) when the client chooses the identifier, since PUT means "make the resource at this URL have this state". Most public APIs with server-generated ids use POST for create.

### Why is PUT idempotent and POST not?

RFC 9110 section 9.2.2 defines a method as idempotent if the intended effect of several identical requests equals the effect of one. PUT replaces the state at a URL, so repeating it leaves the same state. POST has no such guarantee, and sending the same create twice normally creates two resources. A client or proxy may automatically retry an idempotent request after a connection failure; it will not do that for POST.

### Can I make POST safe to retry?

Yes, with an idempotency key. The client sends a unique value such as Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9324 and the server stores the first result under it and replays it for repeats. Stripe popularized the pattern, and there is an IETF draft for a standard header, but it is not yet an RFC.

### Does PUT have to send the whole resource?

Yes. PUT replaces the target resource state with the enclosed representation, so omitted fields are removed or reset. For a partial change use PATCH, covered in the PUT vs PATCH comparison.

### What status code should PUT return?

201 Created if the PUT created the resource, 200 or 204 if it replaced an existing one (RFC 9110 section 9.3.4). Returning 201 for create and 200/204 for replace is what lets a client tell upsert outcomes apart.

### Is PUT supported by HTML forms?

No. HTML forms only submit GET and POST (and dialog). Frameworks fake PUT with a hidden _method field, for example Rails and Laravel, which translate it server-side. From JavaScript, fetch() can send PUT directly, though it triggers a CORS preflight when cross-origin.

## References

- [MDN Web Docs: PUT](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT)
- [MDN Web Docs: POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST)
- [RFC 9110: PUT (section 9.3.4)](https://www.rfc-editor.org/rfc/rfc9110#section-9.3.4)
- [RFC 9110: POST (section 9.3.3)](https://www.rfc-editor.org/rfc/rfc9110#section-9.3.3)
- [RFC 9110: Idempotent Methods (section 9.2.2)](https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2)
