MOVE method

The HTTP MOVE method atomically relocates an element from one position to another. The source element is removed and re-inserted at a destination anchor with a chosen placementappend, prepend, before, or after relative to the anchor.

When to reach for it

Use MOVE to reorder children of a container or to shift an element between containers in a single request. The removal and insertion happen in one transaction; readers never observe a state where the element is missing or duplicated. Schema cardinality is checked at both the source ancestor (after removal) and the destination ancestor (after insertion), so a MOVE that would violate either side fails as a unit.

A successful MOVE returns 204 No Content.

Headers

Header Required Purpose
Range: selector=<source> Yes Selects the source element
Destination: <path> Yes The target document path. For an element move this must equal the request path; a whole-document move may name a different path
Destination-Range: selector=<dest>; placement=<position> Yes (for element moves) Selects the destination anchor and the placement directive

A request is a whole-document move only when both Range and Destination-Range are omitted; the whole document is then relocated to Destination. Supplying Range without Destination-Range (or vice versa) is an incomplete element move and is rejected with 422 Unprocessable Entity.

Placement

placement= accepts one of four values, case-insensitive:

Value Insertion site relative to the destination anchor
append As the last child of the anchor
prepend As the first child of the anchor
before As the previous sibling of the anchor
after As the next sibling of the anchor

Authorization

MOVE is always default-deny. Unlike GET, it is never covered by a host's default-GET mode, so a MOVE that matches no rule is refused even on a host that allows unmatched reads. Every MOVE needs at least one explicit AuthorizationRule.

The three checks

Because a MOVE both removes an element and inserts one, it is not authorized by a MOVE rule alone. An element MOVE is authorized by three separate checks, all of which must pass:

# Method checked Selector checked Granted by a rule for
1 MOVE none The document as a whole
2 DELETE the Range selector (the source element) Removing the source element
3 POST the Destination-Range selector (the destination anchor) Inserting at the destination

A rule granting MOVE therefore permits the operation, while the DELETE and POST rules permit the two element-level edits it performs. Granting MOVE alone is not sufficient — the request is denied at check 2 unless a DELETE rule also covers the source element.

All three checks are evaluated against the request path. An element MOVE cannot cross documents (see Error cases), so the request path is also the destination document.

Check 3 is authorized exactly as a POST to the destination anchor with the same placement — it keys on the element whose child list changes:

So a POST rule that authorizes inserting into a container also authorizes moving an element to append/prepend inside it, or before/after any of its children — no separate rule per placement is needed.

A MOVE rule must not carry a selector

An AuthorizationRule whose method is MOVE must leave selector empty. A MOVE rule that specifies a selector is discarded entirely — it does not merely lose its selector. An Allow rule written that way has no effect at all, and the MOVE is denied by default:

<!-- WRONG: discarded — this grants nothing, and MOVE is denied -->
<tr itemscope itemtype="https://pagelove.org/AuthorizationRule">
  <td itemprop="actor">editors</td>
  <td itemprop="resource">/board.html</td>
  <td itemprop="method">MOVE</td>
  <td itemprop="selector">.card</td>
  <td itemprop="action">Allow</td>
</tr>

Element granularity for a MOVE comes from the DELETE and POST rules in checks 2 and 3, which do take selectors. An empty or whitespace-only selector cell is fine.

Example: allowing editors to reorder cards

Three rules — one per check — let editors move any .card between lanes on a board:

<table itemscope itemtype="https://pagelove.org/AuthorizationRule">
  <tbody>
    <!-- Check 1: permit the MOVE operation on the document (no selector) -->
    <tr>
      <td itemprop="actor">editors</td>
      <td itemprop="resource">/board.html</td>
      <td itemprop="method">MOVE</td>
      <td itemprop="selector"></td>
      <td itemprop="action">Allow</td>
    </tr>

    <!-- Check 2: permit removing a card from its current lane -->
    <tr>
      <td itemprop="actor">editors</td>
      <td itemprop="resource">/board.html</td>
      <td itemprop="method">DELETE</td>
      <td itemprop="selector">.card</td>
      <td itemprop="action">Allow</td>
    </tr>

    <!-- Check 3: permit inserting a card into a lane -->
    <tr>
      <td itemprop="actor">editors</td>
      <td itemprop="resource">/board.html</td>
      <td itemprop="method">POST</td>
      <td itemprop="selector">.lane</td>
      <td itemprop="action">Allow</td>
    </tr>
  </tbody>
</table>

The single POST rule on .lane covers every placement: append/prepend anchor a lane directly, and before/after anchor a .card whose parent is a .lane. So this one container-level rule authorizes both moving a card into a lane and reordering cards within one.

Narrowing check 2 or check 3 narrows which moves are possible. A DELETE rule scoped to #lane-todo > .card permits moving cards out of the todo lane only; a POST rule scoped to #lane-done permits inserting them into the done lane only.

Whole-document MOVE

A MOVE with no Destination-Range relocates a whole document. It has no source or destination selector, so checks 2 and 3 do not apply — but it is authorized by a MOVE rule on both the request path (the source) and the Destination path. Relocating a document is authorized against where it's landing, not only against where it started: a deny rule matching either path refuses the whole request, so a document can't be moved into a location protected by a deny rule just because the source location itself is allowed.

Denials

Condition Status
Any of the three checks denies, and the request is unauthenticated 401 Unauthorized
Any of the three checks denies, and the request is authenticated 403 Forbidden

Authorization for MOVE fails closed: if a selector in check 2 or check 3 matches no element, or the document backing the check cannot be read, the request is denied rather than allowed.

Examples

Move a card to a different lane

Given a board with cards in two lanes, move card 3 to the end of the "done" lane:

MOVE /move-append-test.html
Range: selector=#card-3
Destination: /move-append-test.html
Destination-Range: selector=#lane-done; placement=append
HTTP/1.1 204

Move a sibling before another

Reorder list items by inserting one before another:

MOVE /move-before-test.html
Range: selector=#item-c
Destination: /move-before-test.html
Destination-Range: selector=#item-a; placement=before
HTTP/1.1 204

Error cases

Condition Status
Source (Range) selector matches no element 416 Range Not Satisfiable
Destination (Destination-Range) selector matches no element 404 Not Found
Incomplete element move — only one of Range / Destination-Range supplied, or a missing placement 422 Unprocessable Entity
Whole-document move with no Destination header 422 Unprocessable Entity
Element move whose Destination names a different document 501 Not Implemented
Invalid Range / Destination-Range selector, or an illegal move (e.g. into the element's own descendant) 422 Unprocessable Entity
Authorization denied 401 Unauthorized / 403 Forbidden — see Authorization
Schema cardinality violated at source or destination 422 Unprocessable Entity

Cross-document element moves are not implemented: a MOVE carrying a Destination-Range whose Destination differs from the request path is rejected with 501 Not Implemented and a MoveCrossResource error, before anything is written. Only whole-document MOVE may cross paths; a whole-document move whose Destination names a path with no existing document creates it and returns 204.

A Destination-Range selector that matches no element returns 404 Not Found: the destination element MUST exist, and its absence is a client condition, not a server fault. This mirrors the source-selector case (a Range that matches no element returns 416) — both are reported as client errors rather than a 5xx.

See also