How HTTP Works

Method · unsafe · not idempotent

> PATCH /users/42 HTTP/1.1>  Host: api.example.com>  Content-Type: application/merge-patch+json

HTTP PATCH Method

Learn how HTTP PATCH requests apply partial modifications to resources. Understand JSON Patch, merge patch formats, and when to use PATCH vs PUT.

Reviewed 6 min readintermediate4 sourcesTry itMarkdown
Safe
No
Idempotent
No
Cacheable
Only with explicit freshness
Request body
Yes
Response body
Optional
Spec
RFC 5789 §2
On this page

TL;DR: PATCH updates part of a resource by sending a change document instead of the whole thing. The Content-Type decides what the body means: application/merge-patch+json sends the fields to change, while application/json-patch+json sends a list of operations. PATCH isn’t automatically idempotent, and the server has to apply the whole patch or none of it.

What is PATCH?

PATCH is defined in RFC 5789 §2. The difference from PUT is what you send: PUT sends the new state, PATCH sends instructions for getting there. A patch can touch more than one field, and some patch formats can even replace the entire document.

Key Characteristics

PATCH is unsafe, and it’s only idempotent if its operations are. Setting a field to a fixed value gives the same result every time; appending to an array adds another element on every retry. A string like "current + 1" is just a string unless your API has explicitly defined that syntax as an operation.

Atomicity is a requirement, not a nice-to-have. If any operation fails, none of the patch’s changes get applied, and no other reader ever sees a half-patched resource.

How PATCH Works

Send the format your server says it supports. For a server that accepts JSON Merge Patch:

curl -i -X PATCH 'https://api.example.com/users/42' \
  -H 'Content-Type: application/merge-patch+json' \
  -H 'If-Match: "user-v7"' \
  --data '{"displayName":"Avery","temporaryNote":null}'

The ETag here is a placeholder, so GET the resource first to get its current strong validator. On the server side, merge patch is a specific algorithm with its own media type. Passing parsed JSON straight into a generic ORM update doesn’t implement it.

Real-World Examples

JSON Patch can check a value before changing it:

curl -i -X PATCH 'https://api.example.com/tickets/42' \
  -H 'Content-Type: application/json-patch+json' \
  -H 'If-Match: "ticket-v7"' \
  --data '[{"op":"test","path":"/status","value":"open"},{"op":"replace","path":"/status","value":"closed"}]'

If the test fails, the ticket stays exactly as it was. This assumes the endpoint supports JSON Patch and the ticket has a status member.

Settings and array updates

On a merge-patch endpoint, a couple of boolean settings make a clean partial update:

{"emailNotifications":false,"pushNotifications":true}

Settings you leave out stay as they are. For tags, merge patch needs the complete array you want:

{"tags":["web","api","tutorial"]}

That replaces the old array outright; it won’t merge the two lists or remove a single tag. If you need to change one array position, use JSON Patch, and add a precondition if another editor might have reordered the array in the meantime.

When to Use PATCH

Use PATCH when a documented change format fits better than sending the whole new state. PATCH can even create a resource, if the patch format can operate on something that doesn’t exist yet and the server allows it. So “PATCH can never create” is too strong a rule.

PATCH vs Other Methods

PUT is idempotent by definition, while PATCH is only as idempotent as its operations. POST is for whatever processing the resource defines. Whichever method you pick, validation, authorization and concurrency control are still your job.

PATCH Formats

JSON Merge Patch

RFC 7396 defines application/merge-patch+json. Objects merge recursively. A member set to null gets removed, not stored as a JSON null, and a member you leave out stays unchanged. Arrays are replaced wholesale, so you can’t append one element with this format.

An API that takes application/json with a subset of fields has its own contract, unless it says otherwise. Looking like merge patch doesn’t make it merge patch, so name it after what it actually does.

Here’s why a shallow object spread isn’t enough. Start with:

{"profile":{"name":"Avery","city":"London"},"tags":["web"],"temporaryNote":"draft"}

Apply this as application/merge-patch+json:

{"profile":{"city":"Paris"},"tags":["api"],"temporaryNote":null}

And you get:

{"profile":{"name":"Avery","city":"Paris"},"tags":["api"]}

The name survives because the merge is recursive, the tags array gets replaced, and the temporary note disappears. If your data model needs a member whose value really is null, that removal rule gets in the way. JSON Patch can set a value to null without treating it as a delete.

JSON Patch

RFC 6902 defines application/json-patch+json: an ordered array of add, remove, replace, move, copy and test operations, with paths written as JSON Pointers. An add to /tags/- appends to an existing array, so sending the same patch twice appends twice. replace only works if its target already exists.

Each of the six operations has its own rules:

OperationConcrete behavior
addAdd an object member or insert an array element; /- appends to an array.
removeRemove an existing target.
replaceReplace an existing target.
moveRemove at from, then add at path.
copyCopy the value at from to path.
testCompare the target with value; failure stops application.

JSON Pointer escapes ~ as ~0 and / as ~1. So /a~1b points at a member literally named a/b, not at b inside a. If a patch works for ordinary field names but fails on user-defined keys, check the escaping.

Common Response Codes

A 200 can return the updated representation, and a 204 returns nothing. A failed If-Match normally gets 412. A conflict that isn’t a failed precondition can get 409. For a patch media type the server doesn’t support, send 415 with Accept-Patch listing the formats it does accept. A patch the server understands but can’t apply can get 422.

Getting PATCH right

Ask the server what it supports with OPTIONS:

curl -i -X OPTIONS 'https://api.example.com/users/42'

A response might look like this:

HTTP/1.1 204 No Content
Date: Mon, 05 Oct 2026 12:00:00 GMT
Allow: GET, HEAD, PATCH, OPTIONS
Accept-Patch: application/merge-patch+json, application/json-patch+json

Run authorization and validation against the document that results from the patch, not only against the keys in the request. Check If-Match and write the change in one atomic step, so nothing can slip in between the check and the write.

A PATCH response is cacheable only if it has explicit freshness information and a Content-Location that matches the target URI. Even then, the cached copy can answer later GET or HEAD requests, never another PATCH.

In a browser client, hold on to the ETag you read with the document and send it back:

async function patchDocument(id, changes, etag) {
  const response = await fetch(`/api/documents/${encodeURIComponent(id)}`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/merge-patch+json',
      'If-Match': etag
    },
    body: JSON.stringify(changes)
  })
  if (response.status === 412) throw new Error('Document changed; reload before editing')
  if (!response.ok) throw new Error(`PATCH failed: ${response.status}`)
  return response.status === 204 ? null : response.json()
}

A 412 means someone else changed the document, so retrying blindly would overwrite their work. Reload, reconcile the changes, and submit against the new version. A cross-origin client also needs the server to expose ETag and allow the If-Match request header. If you hit a 415, compare your exact media type with Accept-Patch before you touch the patch body.

Try It Yourself

Try a partial update with the update preset. Check which Content-Type that endpoint accepts before you assume it implements either standard JSON patch format.

On a merge-patch endpoint, compare sending {"tags":["api"]} with leaving tags out. The first replaces the array and the second leaves it alone. An endpoint with its own custom partial-update contract may behave differently.

In practice

Express.js
// PATCH /users/:id — simple partial update with a plain JSON body.
// This is not RFC 7396 JSON Merge Patch: null does not delete a field and nested objects are not merged.
app.patch('/users/:id', async (req, res) => {
  // Only update fields present in the body
  const user = await db.users.update(req.params.id, req.body)
  if (!user) return res.status(404).json({ error: 'Not found' })
  res.json(user)
})

// Client sends only changed fields:
// PATCH /users/42  { "email": "new@example.com" }
// Other fields (name, role, etc.) are untouched
Next.js App Router
// app/api/users/[id]/route.ts
export async function PATCH(
  request: Request,
  { params }: { params: { id: string } }
) {
  const patch = await request.json()
  // Merge patch — only provided fields are updated
  const user = await db.users.update(params.id, patch)
  if (!user) return new Response(null, { status: 404 })
  return Response.json(user)
}
Django
# views.py
import json
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def user_detail(request, user_id):
    if request.method == 'PATCH':
        data = json.loads(request.body)
        # Only update provided fields
        User.objects.filter(pk=user_id).update(**data)
        return JsonResponse(User.objects.get(pk=user_id).to_dict())

Frequently asked questions

What is the HTTP PATCH method?

PATCH applies a change document to the target resource. The document format defines the modifications; PATCH is not limited to assigning values to individual fields.

What is the difference between PATCH and PUT?

PUT supplies replacement state, whereas PATCH supplies instructions for changes. Use the media type and behavior documented by the endpoint.

Is PATCH idempotent?

PATCH is not inherently idempotent. Setting a fixed value can be idempotent; appending to an array can have a new effect on each application.

What format should PATCH use?

JSON Merge Patch uses application/merge-patch+json, and JSON Patch uses application/json-patch+json. Partial application/json bodies need an application-defined contract; they are not automatically either standardized format.

Sources

  1. MDN Web Docs: PATCHdeveloper.mozilla.org
  2. RFC 5789: PATCHrfc-editor.org
  3. RFC 7396: JSON Merge Patchrfc-editor.org
  4. RFC 6902: JSON Patchrfc-editor.org

Keep going

Browse /search