If-Match
request headercaching
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, and the client can reload and merge.
Syntax
If-Match: "<etag>"[, "<etag>"…] or If-Match: *
Defined in RFC 9110 §13.1.1 — If-Match.
Values
| "<etag>" | proceed only if the current version has this ETag |
|---|---|
| * | proceed only if the resource exists — never create it |
Examples
If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
update only the version I read
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 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
ETagthey need if it's listed inAccess-Control-Expose-Headers.
Using If-Match for safe updates
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
}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 >:
curl -sv https://example.com/ -o /dev/null 2>&1 | grep '^>'
Frequently asked questions
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.