# If-Match Header — Syntax, Values & Examples

> Makes a write conditional: apply it only if the resource still has this ETag. Prevents lost updates.

Two people load the same document, both edit, both save — and the second save silently overwrites the first. If-Match prevents that: the client sends back the ETag it read, and the server applies the change only if the current ETag still matches. Otherwise it answers [412 Precondition Failed](https://www.devkult.com/http/412.md), and the client can reload and merge.

- Type: Request header
- Category: Caching
- Defined in: [RFC 9110 §13.1.1 — If-Match](https://www.rfc-editor.org/rfc/rfc9110.html#section-13.1.1)

## Syntax

```http
If-Match: "<etag>"[, "<etag>"…]   or   If-Match: *
```

## Values

| Value | Meaning |
|---|---|
| `"<etag>"` | proceed only if the current version has this ETag |
| `*` | proceed only if the resource exists — never create it |

## Examples

```http
If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
```

update only the version I read

```http
If-Match: *
```

update only if it already exists

## Good to know

- If-Match uses strong comparison, so weak ETags (`W/"…"`) never match.
- APIs can require it: answer [428 Precondition Required](https://www.devkult.com/http/428.md) when a write arrives without one.
- `If-None-Match: *` is the opposite guard — create only if nothing exists yet.
- Cross-origin clients can only read the `ETag` they need if it's listed in `Access-Control-Expose-Headers`.

## Using If-Match for safe updates

### Client (fetch)

```
const res = await fetch("/api/docs/42");
const etag = res.headers.get("ETag");
const doc = await res.json();

const save = await fetch("/api/docs/42", {
  method: "PUT",
  headers: { "Content-Type": "application/json", "If-Match": etag },
  body: JSON.stringify({ ...doc, title: "New title" }),
});
if (save.status === 412) {
  // Someone else changed it first: reload and merge
}
```

### Server (Express)

```
app.put("/api/docs/:id", async (req, res) => {
  const doc = await db.get(req.params.id);
  const current = `"${doc.version}"`;
  const ifMatch = req.get("If-Match");
  if (!ifMatch) return res.status(428).json({ error: "If-Match required" });
  if (ifMatch !== "*" && !ifMatch.split(/\s*,\s*/).includes(current)) {
    return res.status(412).json({ error: "Document has changed" });
  }
  // ...apply the update and send the new ETag
});
```

## Check it

In the browser, open DevTools → Network, pick a request and look under Request Headers. From the command line, curl's verbose mode prints every header it sends, prefixed with `>`:

```sh
curl -sv https://example.com/ -o /dev/null 2>&1 | grep '^>'
```

## FAQ

### What's the difference between If-Match and If-None-Match?

If-Match proceeds only when the ETag matches, which guards writes. If-None-Match proceeds only when it doesn't, which drives cache revalidation (304) and, with *, create-only requests.

## Status codes it appears with

- [412 Precondition Failed](https://www.devkult.com/http/412.md)
- [428 Precondition Required](https://www.devkult.com/http/428.md)

## Related headers

- [ETag](https://www.devkult.com/headers/etag.md): An identifier for a specific version of a resource, used to revalidate cached copies and to prevent lost updates.
- [If-None-Match](https://www.devkult.com/headers/if-none-match.md): Makes a request conditional: send the resource only if its current ETag isn't one of those listed.
- [If-Modified-Since](https://www.devkult.com/headers/if-modified-since.md): Asks the server to send the resource only if it changed after the given date — otherwise a 304.
- [Access-Control-Expose-Headers](https://www.devkult.com/headers/access-control-expose-headers.md): Lists the response headers that JavaScript on another origin is allowed to read.

Open the interactive page: https://www.devkult.com/headers/if-match