CodeToolProCodeToolPro
GitHub
Network·7 min read

PUT vs PATCH: Which HTTP Method Should You Use?

CodeToolPro Team·

PUT vs PATCH: Which HTTP Method Should You Use?

PUT and PATCH both update a resource on the server, but they agree on almost nothing else: PUT replaces the entire resource, while PATCH modifies only the fields you send. Mix them up and you will either wipe fields you meant to keep or fail to replace a value that should have changed. This guide compares PUT vs PATCH across replacement semantics, idempotency, and payload handling. Try both methods live with our API Tester — pick the method, paste a URL, and inspect the real status code, headers, and response body.

What Are PUT and PATCH?

Both are HTTP request methods defined in RFC 9110, and both target a resource identified by the request URL. They differ in how much of that resource they touch:

  • PUT stores the enclosed representation at the target URL, completely replacing whatever was there. If the resource had ten fields and your PUT body contains three, the seven missing fields are gone.
  • PATCH applies a partial modification described by the request body. Fields you do not mention are left untouched.

A useful mental model: PUT is "here is the whole new thing", PATCH is "here are a few changes to apply".

PUT vs PATCH at a Glance

AspectPUTPATCH
IntentReplace entire resourcePartial modification
Fields omitted from bodyDeleted / resetPreserved
Requires full objectYesNo (just the diff)
Idempotent (retry-safe)YesYes (for well-designed PATCH)
BandwidthHigher (sends whole object)Lower (sends only changes)
Typical success status200 OK / 204 No Content200 OK / 204 No Content
Replaces vs mergesReplacesMerges
Must be sent to a known URLYes (client controls the URI)Yes

Full Replacement vs Partial Update

The defining difference is what happens to the fields you do not mention:

  • A PUT is a full-state swap. Sending { "title": "New", "author": "Ada" } to a resource that also had publishedAt and tags means those two properties are dropped. The server ends up storing exactly what you sent — nothing more.
  • A PATCH is a delta. Sending { "title": "New" } changes only title; author, publishedAt, and tags keep their previous values.

This matters for concurrency. With PUT, a slow client holding a stale full copy can overwrite a change another client just made. With PATCH, each request touches only its own fields, reducing accidental clobbering — though two patches to the same field can still race.

Rule of thumb: if the client already has (or can cheaply fetch) the full object, PUT is simple and unambiguous. If it only knows one field changed, PATCH is smaller and safer for the rest of the resource.

Idempotency: Why Both Are Retry-Safe

Both PUT and PATCH are supposed to be idempotent — sending the same request N times yields the same final state as sending it once. That is why browsers and proxies treat repeats calmly:

  • PUT is trivially idempotent: the first call sets the state to X, the second call sets it to X again. No drift.
  • PATCH depends on the patch format. A JSON Merge Patch ({ "title": "X" }) is idempotent because applying it twice produces the same field value. A counter-incrementing patch ({ "views": "+1" }) is not idempotent — every replay adds another view. Use idempotent patch documents, or add an If-Match / ETag precondition to reject stale writes.

Neither PUT nor PATCH is safe (both change server state), so never use them for reads — that is what GET is for. Compare with our GET vs POST breakdown of read vs write methods.

Hands-on: Tested with the Tool

I verified the replacement-vs-merge behavior with the API Tester against the public JSONPlaceholder demo API. Steps to reproduce:

  1. Open the tool, set Method = PUT, enter https://jsonplaceholder.typicode.com/posts/1, add Header Content-Type: application/json, and paste this body: {"id": 1, "title": "Updated Title", "body": "Full replacement", "userId": 1}. Sending returned 200 OK and the body {"id":1,"title":"Updated Title","body":"Full replacement","userId":1} — exactly the fields I sent, nothing inherited from the original post.
  2. Start a fresh request, set Method = PATCH, use the same URL, and send only {"title": "Patched Title Only"}. It returned 200 OK with {"userId":1,"id":1,"title":"Patched Title Only","body":"quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\n..."} — the original body was preserved while only title changed. That is the merge behavior of PATCH.
  3. Both requests reported round-trip time and the same 200 status, but only the PATCH response carried the server's pre-existing body field — the clearest possible proof of replace-vs-merge.

Two observations from the tool's actual implementation: it attaches the request body for every method except GET/HEAD (so PUT and PATCH both carry the JSON body), and it echoes the server's response status and body verbatim, which made the missing-field difference obvious at a glance.

Code Examples: JS and Python

The same pair of requests in JavaScript with fetch:

// PUT: replace the entire resource
const putRes = await fetch("https://jsonplaceholder.typicode.com/posts/1", {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ id: 1, title: "Updated Title", body: "Full replacement", userId: 1 }),
});
console.log(putRes.status);            // 200
console.log(await putRes.json());       // { id:1, title:"Updated Title", body:"Full replacement", userId:1 }

// PATCH: change only the title, keep the rest
const patchRes = await fetch("https://jsonplaceholder.typicode.com/posts/1", {
  method: "PATCH",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ title: "Patched Title Only" }),
});
console.log(patchRes.status);           // 200
const updated = await patchRes.json();
console.log(updated.title);             // "Patched Title Only"
console.log(updated.body);              // original body preserved

And in Python using only the standard library:

import json, urllib.request

BASE = "https://jsonplaceholder.typicode.com/posts/1"

# PUT: full replacement
put_req = urllib.request.Request(
    BASE,
    data=json.dumps({"id": 1, "title": "Updated Title",
                     "body": "Full replacement", "userId": 1}).encode(),
    headers={"Content-Type": "application/json"},
    method="PUT",
)
with urllib.request.urlopen(put_req) as r:
    print(r.status)                      # 200
    print(json.load(r))                  # only the fields we sent

# PATCH: partial update
patch_req = urllib.request.Request(
    BASE,
    data=json.dumps({"title": "Patched Title Only"}).encode(),
    headers={"Content-Type": "application/json"},
    method="PATCH",
)
with urllib.request.urlopen(patch_req) as r:
    print(r.status)                      # 200
    obj = json.load(r)
    print(obj["title"])                  # "Patched Title Only"
    print("body" in obj)                 # True — original body kept

Both snippets were run as-is and produced exactly the statuses and field behavior shown above.

Common Mistakes

  1. Using PUT when you only know one field changed. You end up re-sending a full object fetched minutes ago; meanwhile another client changed status, and your PUT deletes it. Reach for PATCH.
  2. Sending a PATCH expecting a full replace. Editors that lazily PUT the whole form are fine; but if you intend a swap and accidentally PATCH, the old fields linger. Know which your client does.
  3. Assuming PATCH is always idempotent. An increment or "append" patch is not. Guard replays with If-Match: <etag> so a stale write returns 412 Precondition Failed instead of corrupting state — see the exact meaning in our HTTP Status Codes reference.
  4. Forgetting Content-Type. A JSON Merge Patch needs Content-Type: application/json (or application/merge-patch+json). Without it, servers may reject or misparse the body.
  5. PUT to a URL you do not fully own. PUT means "this exact URI now holds exactly this body." Pushing a partial object to a PUT endpoint wipes the rest. Split the URL with the URL Parser if you are unsure what resource you are targeting.

Related Tools

  • API Tester — send GET/POST/PUT/PATCH/DELETE requests and inspect status, headers, body, and timing.
  • URL Parser — split any URL into protocol, host, path, and query so you know exactly which resource you are targeting.
  • HTTP Status Codes — quick reference for 200, 204, 400, 404, 412, and 500.
  • JSON Formatter — pretty-print the JSON request and response bodies you exchange.
  • GET vs POST — how read vs write methods differ in safety, caching, and payload.

When to Use This Tool Instead of Code

Writing a fetch snippet every time you want to confirm a PUT replaced a resource (or a PATCH merged one) is slow: you need a console, boilerplate, and a way to pretty-print the response. The API Tester gives you a method dropdown, header editor, and formatted response in one screen — ideal for checking that your endpoint replaces versus merges, debugging a 412 Precondition Failed, or demonstrating PUT vs PATCH to a teammate. Reach for code when you need automation, authentication flows, or repeated scripted calls; reach for the tool when you need an answer in ten seconds.