# 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.

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

> **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](https://www.rfc-editor.org/rfc/rfc5789.html#section-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:

```bash
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:

```bash
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:

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

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

```json
{"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](https://www.rfc-editor.org/rfc/rfc7396.html) 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:

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

Apply this as `application/merge-patch+json`:

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

And you get:

```json
{"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](https://www.rfc-editor.org/rfc/rfc6902.html) 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:

| Operation | Concrete behavior |
| --- | --- |
| add | Add an object member or insert an array element; `/-` appends to an array. |
| remove | Remove an existing target. |
| replace | Replace an existing target. |
| move | Remove at `from`, then add at `path`. |
| copy | Copy the value at `from` to `path`. |
| test | Compare 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](https://howhttpworks.com/status-codes/412). A conflict that isn't a failed precondition can get [409](https://howhttpworks.com/status-codes/409). For a patch media type the server doesn't support, send [415](https://howhttpworks.com/status-codes/415) with `Accept-Patch` listing the formats it does accept. A patch the server understands but can't apply can get [422](https://howhttpworks.com/status-codes/422).

## Getting PATCH right

Ask the server what it supports with OPTIONS:

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

A response might look like this:

```http
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:

```javascript
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](https://howhttpworks.com/tools/playground?preset=update). 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.

## Related Methods

- [PUT](https://howhttpworks.com/methods/put)
- [POST](https://howhttpworks.com/methods/post)

## Related Concepts

- [If-Match](https://howhttpworks.com/headers/if-match)
- [Idempotent](https://howhttpworks.com/glossary/idempotent)
