PUT vs PATCH

PUT sends the whole resource and replaces it. PATCH sends a change. What that means for idempotency, data loss, formats and which one your API should offer.

Reviewed 2026-09-16

PUT replaces the whole resource with what you send. PATCH changes part of it and leaves the rest alone. PUT is idempotent by definition; PATCH may or may not be, depending on what the patch says.

PUT

  • Body is the complete new representation.
  • Fields you omit are removed.
  • Idempotent: same request twice, same result.
  • Can create the resource at that URL if it does not exist.
  • RFC 9110 §9.3.4.

PATCH

  • Body describes a change, in a format the server documents.
  • Fields you omit are untouched.
  • Not idempotent in general (but a merge patch usually is).
  • Requires the resource to exist.
  • RFC 5789.

The same edit, both ways

A user record has name, email and phone. You want to change the email.

PUT: send everything
PUT /users/42
Content-Type: application/json

{
  "name": "Ada Lovelace",
  "email": "[email protected]",
  "phone": "+44 20 7946 0000"
}
PATCH: send the change
PATCH /users/42
Content-Type: application/merge-patch+json

{
  "email": "[email protected]"
}

If the PUT body had left out phone, a correct server would delete the phone number. That is the property that makes PUT safe to repeat and dangerous to send from a client that has stale data: two people editing different fields will overwrite each other unless the API uses ETags and If-Match.

PATCH formats

PATCH does not define what the body means; the Content-Type does. Two standard formats exist.

JSON Merge Patch (RFC 7396)

Send a JSON object with only the fields to change. null deletes a field. Nested objects are merged recursively; arrays are replaced whole. Simple, readable, and what most APIs mean by PATCH even when they do not name the format.

Content-Type: application/merge-patch+json

{"email": "[email protected]", "phone": null, "address": {"city": "London"}}

JSON Patch (RFC 6902)

Send an array of operations: add, remove, replace, move, copy, test, each with a JSON Pointer path. More verbose, but it can edit inside arrays and can be conditional.

Content-Type: application/json-patch+json

[
  {"op": "replace", "path": "/email", "value": "[email protected]"},
  {"op": "remove",  "path": "/phone"},
  {"op": "add",     "path": "/tags/-", "value": "vip"},
  {"op": "test",    "path": "/version", "value": 7}
]

Idempotency, precisely

Idempotent means repeating the request leaves the server in the same state as sending it once. PUT always has this property: the body is the whole state. A merge patch such as {"email": "x"} is idempotent too. A JSON Patch with {"op": "add", "path": "/tags/-"} is not: applied twice, it appends twice. Because PATCH is not idempotent by contract, HTTP clients and proxies will not retry it automatically after a network failure, whereas they may retry PUT.

If you need retry-safe partial updates, either use a merge patch (and document that it is idempotent) or accept an idempotency key header the way payment APIs do.

Which to offer in an API

  • Offer PATCH with JSON Merge Patch for the common “update a few fields” case. It is what clients want to send and it minimises accidental data loss.
  • Offer PUT when clients hold the complete representation anyway: configuration documents, file-like resources, settings pages that submit the whole form. Combine it with ETags so a stale PUT gets 412 instead of overwriting.
  • Avoid PUT for partial updates. Some frameworks tolerate a partial PUT body and merge it; that makes the method mean PATCH, and breaks clients that rely on the specification.
  • Validate the whole object after applying a patch, not just the fields sent. A patch that removes a required field should fail with 422.

Responses

Both methods return 200 with the updated representation, or 204 with no body. PUT that creates a new resource returns 201. PATCH to a missing resource returns 404. A patch document that cannot be applied (a test operation failed, a path does not exist) returns 409 or 422; a body in the wrong format returns 415.

In code

// fetch
await fetch("/users/42", {
  method: "PATCH",
  headers: { "Content-Type": "application/merge-patch+json", "If-Match": etag },
  body: JSON.stringify({ email: "[email protected]" }),
});

# curl
curl -X PUT   -H 'Content-Type: application/json' -d @user.json https://api.example.com/users/42
curl -X PATCH -H 'Content-Type: application/merge-patch+json' -d '{"email":"[email protected]"}' https://api.example.com/users/42

The properties of every method are summarised in the HTTP methods reference.

navigateEnter openEsc close