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.

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.

↑↓ moveEnter openEsc close