htmx 4.0 is under construction — migration guide

Upsert

Update-or-insert swap strategy for dynamic lists

The upsert extension adds a new swap style that intelligently updates existing elements by ID and inserts new ones, while preserving elements not in the response. This is particularly useful for maintaining dynamic lists where you want to update specific items without replacing the entire container.

Installing

<script src="/path/to/htmx.js"></script> <script src="/path/to/ext/hx-upsert.js"></script>

Usage

Use hx-swap="upsert" to apply the upsert behavior:

<button hx-get="/items" hx-swap="upsert" hx-target="#item-list"> Refresh Items </button> <div id="item-list"> <div id="item-1">Original Item 1</div> <div id="item-2">Original Item 2</div> </div>

When the server responds with:

<div id="item-2">Updated Item 2</div> <div id="item-3">New Item 3</div>

The result will be:

<div id="item-list"> <div id="item-1">Original Item 1</div> <div id="item-2">Updated Item 2</div> <div id="item-3">New Item 3</div> </div>

How It Works

The upsert swap style:

  1. Updates elements with matching IDs (replaces their outerHTML)
  2. Inserts new elements that don’t have matching IDs
  3. Preserves existing elements not present in the response

Configuration

Sorting

Add sort to maintain elements in ascending order by ID:

<div hx-get="/items" hx-swap="upsert sort">

Use sort:desc for descending order:

<div hx-get="/items" hx-swap="upsert sort:desc">

Custom Key Attribute

Use key:attr to sort by a different attribute:

<div hx-get="/items" hx-swap="upsert key:data-priority sort"> <div id="task-2" data-priority="1">High Priority</div> <div id="task-1" data-priority="5">Low Priority</div> </div>

Prepend Unkeyed Elements

By default, elements without IDs are appended. Use prepend to insert them at the beginning:

<div hx-get="/items" hx-swap="upsert prepend">

Combined Modifiers

<div hx-get="/items" hx-swap="upsert sort:desc prepend">

Using with <hx-partial>

You can use <hx-partial> with hx-swap="upsert" for targeted upserts in a single response:

<hx-partial hx-target="#main" hx-swap="innerHTML"> <div>Updated main content</div> </hx-partial> <hx-partial hx-target="#item-list" hx-swap="upsert sort"> <div id="item-2">Updated Item 2</div> <div id="item-5">New Item 5</div> </hx-partial>

Limitations

  • Only elements with id attributes can be matched and updated
  • IDs must be unique across the entire document
  • Sorting uses numeric-aware localeCompare, which may have performance implications for very large lists