swish.state API provides reactive signals for tracking saved item state on the storefront. It’s built on @preact/signals-core - values update automatically when underlying data changes, and any code wrapped in effect() re-runs when the signals it reads change.
Every sample on this page logs to the console. Paste one into the devtools of a store running Swish and watch the values update as you save, unsave, or navigate. The examples page shows the same helpers driving real markup.
itemContext(source?)
Resolves a product and variant context from a DOM element or window.location. Returns a Signal<ItemContext> that updates reactively when the URL or element attributes change.
The source accepts an HTMLElement or window.location:
HTMLElement is passed, the product is resolved in this order:
- If the element has
data-product-idordata-product-handle, those values are used directly. - If the element has
data-item-id, that saved item ID is used. - If no data attributes are found and the element is an anchor, the product is parsed from its
href.
window.location is passed, the product is always parsed from the current page URL. The SDK watches for SPA navigation (pushState, replaceState, popstate) and updates the context when the URL changes.
When only a productHandle is available, the SDK automatically fetches the productId from the Storefront API.
ItemContext
itemContextFromIds(ids)
Builds an itemContext signal from explicit Shopify IDs instead of a DOM element or URL. Use this when you already have product, variant, or item IDs in hand - for example, when integrating with a framework that owns its own data layer (React, Vue) or building UI from server-rendered props.
loading: false and is never updated automatically - it reflects exactly the IDs you passed in. To react to changing IDs (for example, when a parent component receives new props), construct a new context.
ItemContextIds
itemState(context, options?)
Takes an itemContext signal and returns a reactive signal with the full save state for that product, plus methods to save, unsave, and toggle.
Options
Use
match: "product" on product cards that show a default variant but should report saved whenever the customer has saved any variant of the product. With match: "product", save() also ignores variantId and saves the product, so the card’s default variant isn’t pinned to the saved item. unsave() always targets the currently picked savedItemId.
Callbacks can be replaced after construction with state.setOptions() - this is what the React useSwishItem hook uses to keep callback references current across re-renders without rebuilding the signal.
State properties
Methods
state.save()
Saves the product using the productId and variantId from the context, and the quantity and tags options. Triggers the save:item intent. Calls onSave on success.
state.unsave()
Removes the saved item. Triggers the unsave:item intent. Calls onUnsave on success.
state.toggle()
Saves the product if it’s unsaved, or unsaves it if it’s saved. No-op while loading.
state.updateLists()
Opens the list editor for the saved item. Triggers the edit:item-lists intent. Calls onListsUpdate on success, or onUnsave if the customer removed the item from all lists.
state.updateVariant()
Opens the variant selector for the saved item. Triggers the edit:item-variant intent. Calls onVariantUpdate on success.
state.setOptions(options)
Replaces the callbacks, match policy, quantity and tags passed to itemState. Use this to update options without reconstructing the signal - this is how the React hook keeps callback references current across re-renders.
state.dispose()
Tears down internal effects and query subscriptions. Call this when the consumer (component, custom element) unmounts to release the underlying signals.
state.update() is a deprecated alias for state.updateLists() and will be
removed in a future major release.itemCount()
Returns a reactive signal that tracks the total number of saved items across all lists. Automatically refetches when items are added or removed.
Count properties
A badge that hides itself while
loading will not blink empty when a customer saves or unsaves. The last known count stays on the signal until the new number lands.
count.dispose()
Stops the refetch loop and releases the event listener. Call this when the consumer unmounts.
listContext(source)
Resolves which collection of saved items a page or element is about. Returns a Signal<ListContext> with a dispose() method. It is the collection-level counterpart of itemContext and feeds items().
window.location is passed, the SDK reads the same query parameters the built-in wishlist page and share links use, and updates the signal on SPA navigation (pushState, replaceState, popstate):
When an
HTMLElement is passed, data-list-id and data-list-type are read. Without data-list-type, the type is inferred from the id the same way as for a URL. An anchor without data attributes is parsed from its href.
Because the built-in share URL and the view:list intent both target routes.listDetailPagePath, a custom page mounted at that path receives shared-list and deep-link traffic with no extra wiring.
ListContext
context.dispose()
Restores the history hooks installed for a window.location source. A no-op for element sources.
lists(options?)
Returns a reactive signal with the customer’s lists, for tabs, sidebars, and list pickers. Refetches when a list is created, renamed, deleted, or its membership changes, and when the customer signs in.
Options
State properties
The result does not include an “All items” entry. Build one from
itemCount() and an items({ listType: "all" }) collection, as in the drawer example. Smart lists are feeds Swish fills for the customer, and you can’t add items to them. The two available are recently-viewed and saved-from-cart. Remove a single item with swish.api.smartLists.removeItem. The signal loads a single page of lists.
Methods
lists.refresh()
Refetches the lists. Returns a promise that resolves when the fetch settles.
lists.dispose()
Stops refetching and releases the event listener.
items(context, options?)
Returns a reactive, paginated collection of saved items for one list, or for everything the customer has saved. Takes a listContext signal or a plain { listId, listType } object. Pass a signal you own and write to it to switch lists without rebuilding the collection.
items.value and render from it. Fetch the next page with loadMore(), and the effect runs again with the longer list:
items reads the listContext signal, navigating to ?list-id=... or ?public-list-id=... switches the collection without a reload.
Each item carries Shopify IDs only (id, productId, variantId, quantity, createdAt). Resolve product data per card with product() so cards that never scroll into view never trigger a request.
The collection keeps itself in sync with the SDK’s events, whether the change came from your UI or the built-in Swish UI in the same tab. Changes made in another tab are not delivered; call refresh() if you need to pick those up:
A refetch reloads the first page only. Items from later pages are replaced by that page, and
hasMore reflects the new cursor.
Options
State properties
A guest who has never saved anything has no Swish profile yet. The collection
resolves to an empty page, not an error and not
loading. Their first save
creates the profile and fires token-update, which fills the collection.Methods
items.loadMore()
Fetches the next page and appends it. No-op when hasMore is false or a fetch is already in flight.
items.refresh()
Refetches the first page. The current items stay on the signal until the new page lands.
items.dispose()
Stops syncing, releases the event listener, and ignores any response still in flight.
Actions on items go through intents, so a custom page uses the same vocabulary as the built-in UI and the collection reacts to the resulting events:
list-delete yourself if the page should navigate away.
product(ids)
Returns a reactive signal with render-ready product data for one saved item. It wraps swish.storefront.loadResolvedProduct and shares its cache, so many cards for the same product make one request, and combined listings resolve the way they do in the built-in UI.
ProductIds
State properties
product.dispose()
Releases the cache subscription. Call it when the card unmounts.
Declarative elements
For drop-in save buttons and drawer triggers, use the<swish-save> and <swish-drawer> custom elements. They wrap a merchant-provided button, bind the signals on this page to its markup, and reflect state via attributes you can target with CSS. The examples page shows each element next to its state helper equivalent.
Signal primitives
The SDK re-exportssignal, computed, and effect from @preact/signals-core for building custom reactive logic.
signal(initialValue)
Creates a reactive value. Reading .value inside an effect subscribes to changes.