# HTTP PUT Method: Update Resources

> Learn how the HTTP PUT method works, when to use PUT vs POST vs PATCH, and best practices for updating resources in REST APIs.

Source: https://howhttpworks.com/methods/put
Last reviewed: 2026-10-05

> **TL;DR:** PUT creates or replaces the state of the resource at the target URL. Repeating the same request has the same intended effect, but can return a different status. Use `If-Match` to avoid overwriting a concurrent edit.

## What is PUT?

The client chooses the target URL and submits a replacement representation. A server can reject the representation if it violates the resource's constraints. PUT does not prescribe database columns or require an API to erase every omitted field; a missing required field can be a validation error.

Illustrative request. The body is 35 UTF-8 bytes with no trailing newline:

```http
PUT /documents/notes HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 35
If-Match: "notes-v7"

{"title":"Notes","text":"Reviewed"}
```

## PUT vs POST vs PATCH

PUT supplies replacement state at a known URL. POST requests processing under that target's own rules; creation with a server-assigned URL is one use. PATCH supplies a change document whose format defines how to apply it. An ORM's partial update helper does not automatically implement PUT semantics.

## Idempotency

A first PUT might create a resource and return 201; an identical retry might replace it and return 204. That remains idempotent because the intended stored state is the same. The server can log each attempt or keep revision history.

Idempotency permits retry after a connection failure, but does not prevent a retry from overwriting somebody else's intervening update. Preconditions solve a different problem.

## Implementation

This runnable Express example replaces an in-memory document. The storage is deliberately process-local and only demonstrates the method:

```javascript
const express = require('express')
const app = express()
const documents = new Map()
app.use(express.json())
app.put('/documents/:id', (req, res) => {
  const { title, text } = req.body ?? {}
  if (typeof title !== 'string' || typeof text !== 'string') {
    return res.status(400).json({ error: 'title and text must be strings' })
  }
  const existed = documents.has(req.params.id)
  documents.set(req.params.id, { title, text })
  if (existed) return res.status(204).end()
  res.location(`/documents/${encodeURIComponent(req.params.id)}`)
    .status(201).json({ title, text })
})
app.listen(3000)
```

Run against it:

```bash
curl -i -X PUT 'http://localhost:3000/documents/notes' \
  -H 'Content-Type: application/json' \
  --data '{"title":"Notes","text":"Reviewed"}'
```

Run the same command again and compare the status.

A client must accept an empty success response as well as a JSON representation:

```javascript
async function replaceDocument(id, document) {
  const response = await fetch(`/api/documents/${encodeURIComponent(id)}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(document)
  })
  if (!response.ok) throw new Error(`PUT failed: ${response.status}`)
  return response.status === 204 ? null : response.json()
}
```

This client assumes JSON on nonempty successful responses. Send all writable fields required by the endpoint, not server-controlled fields copied blindly from GET. The server still has to validate the submitted document; client validation cannot protect an API from another caller.

## PUT for Create (Upsert)

Creation is permitted, not required: an update-only API can reject an unknown URL. If PUT creates the target's representation, the server must send 201. Replacing an existing representation successfully requires 200 or 204. To make a creation conditional on absence, send `If-None-Match: *` and have the server evaluate it atomically with the write. See [RFC 9110 §9.3.4](https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.4).

```bash
curl -i -X PUT 'https://api.example.com/documents/new-notes' \
  -H 'If-None-Match: *' \
  -H 'Content-Type: application/json' \
  --data '{"title":"Notes","text":"Reviewed"}'
```

With that precondition enforced, a competing creator cannot silently turn this attempt into replacement. A failed precondition is normally 412. Decide whether the caller should pick a different URL or retrieve the existing document; do not automatically remove the condition and overwrite it.

## Common Response Codes

[201](https://howhttpworks.com/status-codes/201) means creation; [200](https://howhttpworks.com/status-codes/200) or [204](https://howhttpworks.com/status-codes/204) means a successful replacement. [415](https://howhttpworks.com/status-codes/415) can reject an unsupported media type. A failed `If-Match` normally returns [412](https://howhttpworks.com/status-codes/412), not 409; 409 describes a conflict with resource state more generally.

## Getting PUT right

Define the writable representation and validate it before replacing state. Protect server-controlled fields such as owner and role. PUT responses are not cacheable; a successful unsafe request invalidates the cached target under [RFC 9111 §4.4](https://www.rfc-editor.org/rfc/rfc9111.html#section-4.4).

## Optimistic Concurrency with ETags

Read the representation and its strong ETag, then submit it with the replacement:

```bash
curl -i 'https://api.example.com/documents/notes'
curl -i -X PUT 'https://api.example.com/documents/notes' \
  -H 'If-Match: "notes-v7"' \
  -H 'Content-Type: application/json' \
  --data '{"title":"Notes","text":"Reviewed"}'
```

The ETag above is illustrative; use the one your GET returned. `If-Match` uses strong comparison, so a weak tag such as `W/"notes-v7"` cannot satisfy it. The server must check the version and write as one atomic operation. A separate read, string comparison, and unconditional database update leaves a race. The header can contain a list of tags or `*`, so checking it as one arbitrary string is incomplete.

A successful PUT must not return a new validator if the request representation was transformed before storage; see the validator rule in §9.3.4.

## Related

- [PATCH](https://howhttpworks.com/methods/patch)
- [If-Match](https://howhttpworks.com/headers/if-match)
- [PUT vs POST](https://howhttpworks.com/compare/put-vs-post)
