ToolSura
    HomeTools
    Blog
    ToolSuraPrivacy-First Tools

    Free utilities that run in your browser. No trackers, no accounts, no uploads.

    All Systems Operational

    Product

    • Free Online Tools
    • Contact
    • FAQs
    • About

    Legal

    • Privacy Policy
    • Cookie Policy
    • Terms & Conditions

    Resources

    • Blog
    • Brand
    • Help

    Social Links

    • Bluesky
    • Mastodon
    • X
    • Product Hunt
    • GitHub
    • LinkedIn
    • DEV.to
    • YouTube

    © 2026 ToolSura. Free tools that run in your browser.

    Remote-First / Based in India

    Technical Manifesto

    Private • Client-Side • No Uploads

    ToolSura on Nick Launches
    Browser-Native
    Privacy-First
    Skip to main content
    Toolsura
    developer-tools
    A
    Abhay Khant

    POST vs PUT vs PATCH: one difference that explains the other two

    October 8, 2026 · 10 min read

    POST, PUT and PATCH differ by what the request body is allowed to mean. Where RFC 9110 and RFC 5789 draw the line, and why Idempotency-Key has no RFC.

    Three HTTP request blocks side by side over a dark terminal background: POST /charges with a JSON body, PUT /jobs/123 with a full replacement document, and PATCH /jobs/123 with a merge-patch body plus ETag and If-Match headers
    Three HTTP request blocks side by side over a dark terminal background: POST /charges with a JSON body, PUT /jobs/123 with a full replacement document, and PATCH /jobs/123 with a merge-patch body plus ETag and If-Match headers

    The two canonical threads on this question have accumulated 3.7 million and 1.06 million views since 2011 and 2014. That is a decade of community interest, not search volume. No keyword data was available, and none is implied. The most-linked explanation of POST vs PUT vs PATCH is anchored to RFC 7231, obsoleted in June 2022.

    The difference is not a table of verbs. POST and PUT differ in what the request body is allowed to mean: PUT's body replaces the whole resource, POST's body is material the target interprets by its own rules, and PATCH's body is a set of instructions. Nearly every other argument about these three falls out of that one distinction, including the one that costs the most in production: idempotency is a property of the operation, not something the method hands you. POST /charges with a byte-identical body creates two charges. The fix is a header the caller supplies, not a different verb.

    The three properties people collapse into two

    Safe, per RFC 9110 §9.2.1

    Request methods are considered 'safe' if their defined semantics are essentially read-only; i.e., the client does not request, and does not expect, any state change on the origin server as a result of applying a safe method to a target resource.

    RFC 9110 is Standards Track, STD: 97, June 2022, and it is the current definition of method semantics. It obsoletes RFC 2818, 7230, 7231, 7232, 7233, 7235, 7538, 7615 and 7694. Safe methods are GET, HEAD, OPTIONS and TRACE, and safety is what a proxy may do unasked: prefetch, crawl, replay.

    Idempotent, per RFC 9110 §9.2.2

    A request method is considered 'idempotent' if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request. Of the request methods defined by this specification, PUT, DELETE, and safe request methods are idempotent.

    PUT and DELETE are idempotent; POST and PATCH are not. Two clauses in the same section keep that from licensing blind retries:

    A client SHOULD NOT automatically retry a request with a non-idempotent method unless it has some means to know that the request semantics are actually idempotent, regardless of the method, or some means to detect that the original request was never applied.

    and on what idempotency does not buy:

    the idempotent property only applies to what has been requested by the user; a server is free to log each request separately, retain a revision control history, or implement other non-idempotent side effects for each idempotent request.

    The third property is not in the spec at all

    The gap between "POST is not idempotent" and "hand-roll deduplication" is bridged by a header with no standard behind it. Idempotency-Key is draft-ietf-httpapi-idempotency-key-header-06, an IETF Internet-Draft reading "Expires: 28 August 2025". Its abstract states the purpose: "The HTTP Idempotency-Key request header field can be used to make non-idempotent HTTP methods such as POST or PATCH fault-tolerant." It expired unpublished, so there is no standard to cite. What remains is a de-facto convention and a lapsed attempt.

    Stripe's documentation states the method asymmetry in two sentences:

    All POST requests accept idempotency keys. Don't send idempotency keys in GET and DELETE requests because it has no effect. These requests are idempotent by definition.

    A key on a GET does nothing because the method is already idempotent. That is exactly why POST needs one. Stripe caches the first request's status and body per key, "regardless of whether it succeeds or fails", and replays that to any retry, including a 500.

    POST and PUT, in the spec's own words

    The paragraph that settles it

    RFC 9110 §9.3.4 is the most precise statement written about this question:

    The fundamental difference between the POST and PUT methods is highlighted by the different intent for the enclosed representation. The target resource in a POST request is intended to handle the enclosed representation according to the resource's own semantics, whereas the enclosed representation in a PUT request is defined as replacing the state of the target resource. Hence, the intent of PUT is idempotent and visible to intermediaries, even though the exact effect is only known by the origin server.

    POST delegates; PUT replaces. §9.3.4 pins status codes normatively, which PATCH's RFC does not:

    If the target resource does not have a current representation and the PUT successfully creates one, then the origin server MUST inform the user agent by sending a 201 (Created) response. If the target resource does have a current representation and that representation is successfully modified in accordance with the state of the enclosed representation, then the origin server MUST send either a 200 (OK) or a 204 (No Content) response to indicate successful completion of the request.

    PUT can create. That is a MUST, not an anecdote.

    The hedge — and why "PUT wipes your fields" is folklore

    The same section then declines to define what everyone assumes it defines:

    HTTP does not define exactly how a PUT method affects the state of an origin server beyond what can be expressed by the intent of the user agent request and the semantics of the origin server response.

    HTTP states the replacement intent and stops. It does not say a missing member is deleted, nulled or defaulted: that mapping belongs to the server. "My fields got wiped because I left them out" is a common behaviour, not a guarantee: a server that merges on PUT is equally compliant. Inspect the exact body before a PUT rather than trusting the verb.

    POST is not "create"

    A service that selects a proper URI on behalf of the client, after receiving a state-changing request, SHOULD be implemented using the POST method rather than PUT.

    POST is for when the server names the thing, which is the normal case for creation. §9.3.3 covers processing a submission, appending to an existing representation, and recording data, not only creation. MDN's PATCH reference makes the related point: a PUT overwrites an auto-incrementing counter because it replaces the resource, a PATCH may not.

    PATCH is not one thing

    PATCH is defined by RFC 5789, Standards Track, March 2010, and it is current: the header carries no Obsoletes line, and the IANA HTTP Method Registry still cites only RFC 5789 §2 for it. Note where it is not: PATCH is absent from RFC 9110's §9.1 method table and appears there only in incidental cross-references, so reading RFC 9110 cover to cover will never tell you PATCH exists.

    Media type Defined in Body shape What null does
    application/json-patch+json RFC 6902, Standards Track Array of operations n/a — ops are explicit
    application/merge-patch+json RFC 7396, obsoletes 7386 Partial document Removes the member
    application/x-www-form-urlencoded No RFC Implicit per-field merge Implementation-defined
    Vendor type, e.g. application/vnd.github+json No RFC Vendor-shaped partial Vendor-defined

    RFC 5789 §2 explains why there is no default:

    Therefore, there is no single default patch document format that implementations are required to support. Servers MUST ensure that a received patch document is appropriate for the type of resource identified by the Request-URI.

    No RFC forbids the form-encoded PATCH; the spec declines to pick a winner.

    JSON Patch is an array, not a document

    RFC 6902 is Standards Track and not obsoleted. Its body is a list of operations against a target, each with an explicit verb (add, remove, replace, move, copy, test) and a JSON Pointer path. Nothing is inferred from absence; the cost is verbosity.

    [
      { "op": "replace", "path": "/status", "value": "shipped" },
      { "op": "remove",  "path": "/deprecated_flag" }
    ]
    

    JSON Merge Patch is where null deletes

    RFC 7396 takes the opposite approach: a partial document. Three lines of its §2 pseudocode are the ones people get wrong:

    define MergePatch(Target, Patch):
      if Patch is an Object:
        if Target is not an Object:
          Target = {}
        for each Name/Value pair in Patch:
          if Value is null:
            if Name exists in Target:
              remove the Name/Value pair from Target
          else:
            Target[Name] = MergePatch(Target[Name], Value)
        return Target
      else:
        return Patch
    

    "field": null does not set a field to null. It removes it. §1 says so in prose:

    Null values in the merge patch are given special meaning to indicate the removal of existing values in the target.

    Merge patch therefore cannot express "set this field to null". §1 draws the boundary:

    This design means that merge patch documents are suitable for describing modifications to JSON documents that primarily use objects for their structure and do not make use of explicit null values. The merge patch format is not appropriate for all JSON syntaxes.

    Config files are where this bites: a format that can hold null is exactly the case merge patch cannot serve. Diffing before and after catches it.

    What the internet actually sends

    Two things the docs rarely say. GitHub's REST API uses PATCH /repos/{owner}/{repo} with a vendor media type, not either RFC's format: a nested partial applied after reading current state with a GET. Vendor-typed partials are most real PATCH traffic. Atomicity is a MUST (RFC 5789 §2):

    The server MUST apply the entire set of changes atomically and never provide (e.g., in response to a GET during this operation) a partially modified representation.

    A half-applied patch another reader can observe is a spec violation, not a rounding difference.

    Why PUT is idempotent and you still lose writes

    Idempotency protects against your retries. It does nothing about someone else writing between your read and your write. Conditional requests, RFC 9110 §13, close that gap:

    If-Match = "*" / #entity-tag
    

    The round trip uses the validator §9.3.4 gives you for this:

    The new validator(s) received in the response can be used for future conditional requests in order to prevent accidental overwrites (Section 13.1).

    GET /jobs/123
    → 200 OK
      ETag: "e0023aa4f"
    
    PUT /jobs/123
    If-Match: "e0023aa4f"
    → 204 No Content
      ETag: "a91b7c33d"
    

    If anyone else moved the resource in between, the tag no longer matches and the server returns 412 Precondition Failed instead of clobbering their write. Replay with the fresh tag and it succeeds. That is the answer to "PUT is idempotent, so why do we still lose updates?"

    Status codes: which are normative, which are convention

    Situation Status Authority
    PUT created a resource 201 RFC 9110 §9.3.4 — MUST
    PUT updated a resource 200/204 RFC 9110 §9.3.4 — MUST
    POST created a resource 201 + Location RFC 9110 §9.3.3 — SHOULD
    PATCH applied 204 typical RFC 5789 §2.1 — "other success codes could be used as well"
    DELETE enacted, nothing to return 204 typical RFC 9110 §9.3.5 — SHOULD, not MUST
    DELETE pending, not enacted 202 RFC 9110 §9.3.5 — SHOULD
    Method known, resource rejects it 405 + Allow RFC 9110 §15.5.6
    If-Match did not hold 412 RFC 5789 §2.2
    Patch format unsupported 415 + Accept-Patch RFC 5789 §2.2
    Patch understood, unprocessable 422 RFC 5789 §2.2
    Duplicate key, different body 422 expired draft -06, aspirational
    Duplicate key in flight 409 expired draft -06, aspirational

    Read the Authority column before writing an implementation note. PUT's statuses are MUSTs; PATCH's are not, because §2.1 justifies its 204 example by saying "other success codes could be used as well". DELETE has no mandatory status. §9.3.5 recommends 202, 204 or 200 with SHOULD.

    405 is not 501

    The 405 (Method Not Allowed) status code indicates that the method received in the request-line is known by the origin server but not supported by the target resource.

    501 Not Implemented is for a method the server does not implement at all. So POST /jobs/123 against a read-only resource returns 405 with Allow: GET, HEAD. A browser learns the same from a CORS preflight, as our preflight guide explains.

    Two claims that are wrong

    PUT is not "update" and POST is not "create"

    Neither method connects to what the operation does to your database, and both verbs are chosen before anyone knows whether anything is created or modified. POST /refunds can modify an existing refund's metadata; PUT /refunds/9 can create refund number 9, and must answer 201 when it does. What separates them is who names the URI and what the body means.

    Idempotency is not a property of the method

    It is a property of the operation; the method is only a hint. PUT /jobs/123 with a fixed body is idempotent. POST /charges with that same body is not: it creates a second charge. "Just use PUT instead of POST" is backwards: switching verbs changes your URI contract, breaks clients, and may be impossible when the server assigns the identifier. The fix is the same Idempotency-Key on the retry.

    RFC 5789 §2 concedes the same about PATCH: "PATCH is neither safe nor idempotent as defined by [RFC2616], Section 9.1", then immediately "A PATCH request can be issued in such a way as to be idempotent." RFC 2616 is itself obsolete; RFC 9110 §9.2.2 holds those definitions now.

    Method reference

    Safe and idempotent per RFC 9110 §9.2.1 and §9.2.2:

    Method Safe Idempotent What the body means Defined in
    GET Yes Yes No defined semantics RFC 9110 §9.3.1
    HEAD Yes Yes As GET, no body RFC 9110 §9.3.2
    OPTIONS Yes Yes Communicates support RFC 9110 §9.3.7
    POST No No Material the resource interprets RFC 9110 §9.3.3
    PUT No Yes The replacement state RFC 9110 §9.3.4
    PATCH No No by default Instructions RFC 5789 §2
    DELETE No Yes No defined semantics RFC 9110 §9.3.5

    Related Tools & Further Reading

    • JSON Data Viewer — read the exact body before a PUT.
    • JSON Diff & Compare — confirm what a merge patch did when a member vanished instead of becoming null.
    • Why CORS Preflight Failed — the same semantics, from an OPTIONS request.
    A

    Written by

    Abhay Khant

    Abhay Khant is the founder of ToolSura, a privacy-first developer tools platform. Writes about client-side architecture, AI tooling, and the open web.

    Share

    Frequently Asked Questions

    PreviousCertificate Lifetimes: 90 Days to 64 Days in February 2027NextJSON Content-Type: application/json vs text/json

    Comments

    Leave a Review

    Rate this tool
    Overall Rating
    Spam Protection Active